round () cho float trong C ++


232

Tôi cần một hàm làm tròn dấu phẩy động đơn giản, do đó:

double round(double);

round(0.1) = 0
round(-0.1) = 0
round(-0.9) = -1

Tôi có thể tìm ceil()floor()trong math.h - nhưng không round().

Có phải nó hiện diện trong thư viện C ++ tiêu chuẩn dưới tên khác, hay nó bị thiếu ??


1
Nếu bạn chỉ muốn xuất số dưới dạng số làm tròn, có vẻ như bạn chỉ có thể làm được std::cout << std::fixed << std::setprecision(0) << -0.9.
Frank

43
Bảo vệ điều này ... Người dùng mới với các sơ đồ làm tròn mới tuyệt vời nên đọc câu trả lời hiện có trước.
Shog9

12
roundcó sẵn kể từ C ++ 11 in <cmath>. Thật không may nếu bạn đang ở trong Microsoft Visual Studio thì nó vẫn bị thiếu: connect.microsoft.com/VisualStudio/feedback/details/775474/
Kẻ

3
Như tôi lưu ý trong câu trả lời của tôi, lăn của riêng bạn roundcó rất nhiều cảnh báo. Trước C ++ 11, tiêu chuẩn dựa trên C90 không bao gồm round. C ++ 11 dựa trên C99 có roundnhưng cũng như tôi đã lưu ý bao gồm trunccác thuộc tính khác nhau và có thể phù hợp hơn tùy thuộc vào ứng dụng. Hầu hết các câu trả lời dường như cũng bỏ qua rằng người dùng có thể muốn trả về một loại tích phân có nhiều vấn đề hơn.
Shafik Yaghmour

2
@uvts_cvs điều này dường như không phải là vấn đề với phiên bản mới nhất của studio hình ảnh, hãy xem trực tiếp .
Shafik Yaghmour

Câu trả lời:


144

Không có vòng () trong thư viện chuẩn C ++ 98. Bạn có thể tự viết một cái. Sau đây là cách thực hiện nửa vòng :

double round(double d)
{
  return floor(d + 0.5);
}

Lý do có thể xảy ra là không có chức năng tròn trong thư viện chuẩn C ++ 98 là trên thực tế nó có thể được thực hiện theo nhiều cách khác nhau. Trên đây là một cách phổ biến nhưng có những cách khác như làm tròn , thậm chí ít sai lệch và thường tốt hơn nếu bạn sẽ thực hiện nhiều thao tác làm tròn; nó phức tạp hơn một chút để thực hiện mặc dù.


53
Điều này không xử lý số âm một cách chính xác. Câu trả lời của litb là chính xác.
Người dùng đã đăng ký

39
@InnerJoin: Có, nó xử lý các số âm khác với câu trả lời của litb, nhưng điều đó không làm cho nó "không chính xác".
Roddy

39
Thêm 0,5 trước khi cắt không thành tròn cho số nguyên gần nhất cho một số đầu vào bao gồm 0.49999999999999994. Xem blog.frama-c.com/index.php?post/2013/05/02/gầnbyintf1
Pascal Cuoq

10
@ Sergi0: Không có "chính xác" và "không chính xác" bởi vì có nhiều hơn một định nghĩa về làm tròn quyết định những gì xảy ra ở điểm giữa chừng. Kiểm tra sự thật của bạn trước khi thông qua phán xét.
Jon

16
@MuhammadAnnaqeeb: Bạn nói đúng, mọi thứ đã được cải thiện rất nhiều kể từ khi phát hành C ++ 11. Câu hỏi này đã được hỏi và trả lời trong một thời điểm khác khi cuộc sống khó khăn và niềm vui rất ít. Nó vẫn còn ở đây như một điềm báo cho những anh hùng đã sống và chiến đấu trở lại và cho những linh hồn đáng thương vẫn không thể sử dụng các công cụ hiện đại.
Andreas Magnusson

96

Boost cung cấp một bộ chức năng làm tròn đơn giản.

#include <boost/math/special_functions/round.hpp>

double a = boost::math::round(1.5); // Yields 2.0
int b = boost::math::iround(1.5); // Yields 2 as an integer

Để biết thêm thông tin, xem tài liệu Boost .

Chỉnh sửa : Kể từ C ++ 11, có std::round, std::lroundstd::llround .


2
Tôi đã sử dụng boost trong dự án của mình, +1 cho việc này, tốt hơn nhiều so với sử dụng floor(value + 0.5)phương pháp ngây thơ !
Gustavo Maciel

@GustavoMaciel Tôi biết tôi hơi trễ trò chơi, nhưng tăng cường thực hiện floor(value + 0.5).
n. 'đại từ' m.

Nó thực sự không: github.com/boostorg/math/blob/develop/include/boost/math/ trộm 4 năm sau, tôi cũng muốn nói rằng điều đó floor(value + 0.5)không ngây thơ chút nào, mà phụ thuộc vào bối cảnh và bản chất giá trị bạn muốn làm tròn!
Gustavo Maciel

84

Tiêu chuẩn C ++ 03 dựa trên tiêu chuẩn C90 cho tiêu chuẩn gọi Thư viện C tiêu chuẩn được nêu trong dự thảo tiêu chuẩn C ++ 03 ( tiêu chuẩn dự thảo có sẵn công khai gần nhất với C ++ 03 là N1804 ) phần 1.2 Tham khảo quy định :

Thư viện được mô tả trong khoản 7 của ISO / IEC 9899: 1990 và khoản 7 của ISO / IEC 9899 / Amd.1: 1995 sau đây được gọi là Thư viện C tiêu chuẩn. 1)

Nếu chúng ta đi đến tài liệu C cho vòng, lround, llround trên cppreference, chúng ta có thể thấy rằng các hàm tròn và các hàm liên quan là một phần của C99 và do đó sẽ không có sẵn trong C ++ 03 hoặc trước đó.

Trong C ++ 11, điều này thay đổi do C ++ 11 dựa trên tiêu chuẩn dự thảo C99 cho thư viện chuẩn C và do đó cung cấp std :: round và cho các kiểu trả về tích phân std :: lround, std :: llround :

#include <iostream>
#include <cmath>

int main()
{
    std::cout << std::round( 0.4 ) << " " << std::lround( 0.4 ) << " " << std::llround( 0.4 ) << std::endl ;
    std::cout << std::round( 0.5 ) << " " << std::lround( 0.5 ) << " " << std::llround( 0.5 ) << std::endl ;
    std::cout << std::round( 0.6 ) << " " << std::lround( 0.6 ) << " " << std::llround( 0.6 ) << std::endl ;
}

Một tùy chọn khác từ C99 sẽ là std :: trunc :

Tính số nguyên gần nhất không lớn hơn độ lớn.

#include <iostream>
#include <cmath>

int main()
{
    std::cout << std::trunc( 0.4 ) << std::endl ;
    std::cout << std::trunc( 0.9 ) << std::endl ;
    std::cout << std::trunc( 1.1 ) << std::endl ;

}

Nếu bạn cần hỗ trợ các ứng dụng không phải C ++ 11, cách tốt nhất của bạn là sử dụng boost round, iround, lround, llround hoặc boost trunc .

Xoay vòng phiên bản của riêng bạn là khó

Việc tự lăn của bạn có lẽ không đáng để nỗ lực hơn so với vẻ ngoài của nó: làm tròn số phao đến số nguyên gần nhất, phần 1 , Làm tròn số nổi đến số nguyên gần nhất, phần 2Làm tròn số nổi với số nguyên gần nhất, phần 3 giải thích:

Ví dụ, một cuộn phổ biến mà việc triển khai của bạn sử dụng std::floorvà thêm 0.5không hoạt động đối với tất cả các đầu vào:

double myround(double d)
{
  return std::floor(d + 0.5);
}

Một đầu vào này sẽ thất bại cho 0.49999999999999994, ( xem nó trực tiếp ).

Một cách thực hiện phổ biến khác liên quan đến việc chuyển một loại dấu phẩy động thành một kiểu tích phân, có thể gọi hành vi không xác định trong trường hợp phần không thể được biểu diễn trong loại đích. Chúng ta có thể thấy điều này từ bản dự thảo C ++ 4.9 chuyển đổi tích phân nổi có nội dung ( nhấn mạnh của tôi ):

Giá trị của loại dấu phẩy động có thể được chuyển đổi thành giá trị của loại số nguyên. Việc cắt giảm chuyển đổi; đó là, phần phân đoạn bị loại bỏ. Hành vi không được xác định nếu giá trị bị cắt không thể được biểu diễn trong loại đích. [...]

Ví dụ:

float myround(float f)
{
  return static_cast<float>( static_cast<unsigned int>( f ) ) ;
}

Đưa ra std::numeric_limits<unsigned int>::max()4294967295cuộc gọi sau:

myround( 4294967296.5f ) 

sẽ gây ra tràn, ( xem nó trực tiếp ).

Chúng ta có thể thấy điều này thực sự khó khăn như thế nào bằng cách xem câu trả lời này cho cách ngắn gọn để thực hiện vòng () trong C? trong đó tham chiếu phiên bản newlibs của vòng nổi chính xác duy nhất. Đó là một chức năng rất dài cho một cái gì đó có vẻ đơn giản. Dường như bất kỳ ai mà không có kiến ​​thức sâu sắc về việc triển khai dấu phẩy động đều có thể thực hiện chính xác chức năng này:

float roundf(x)
{
  int signbit;
  __uint32_t w;
  /* Most significant word, least significant word. */
  int exponent_less_127;

  GET_FLOAT_WORD(w, x);

  /* Extract sign bit. */
  signbit = w & 0x80000000;

  /* Extract exponent field. */
  exponent_less_127 = (int)((w & 0x7f800000) >> 23) - 127;

  if (exponent_less_127 < 23)
    {
      if (exponent_less_127 < 0)
        {
          w &= 0x80000000;
          if (exponent_less_127 == -1)
            /* Result is +1.0 or -1.0. */
            w |= ((__uint32_t)127 << 23);
        }
      else
        {
          unsigned int exponent_mask = 0x007fffff >> exponent_less_127;
          if ((w & exponent_mask) == 0)
            /* x has an integral value. */
            return x;

          w += 0x00400000 >> exponent_less_127;
          w &= ~exponent_mask;
        }
    }
  else
    {
      if (exponent_less_127 == 128)
        /* x is NaN or infinite. */
        return x + x;
      else
        return x;
    }
  SET_FLOAT_WORD(x, w);
  return x;
}

Mặt khác, nếu không có giải pháp nào khác có thể sử dụng thì newlib có thể có khả năng là một lựa chọn vì đây là một triển khai được thử nghiệm tốt.


5
@downvoter vui lòng giải thích những gì có thể được cải thiện? Phần lớn câu trả lời ở đây chỉ sai vì họ cố gắng tự xoay vòng mà tất cả đều thất bại ở dạng này hay dạng khác. Nếu có gì thiếu sót trong lời giải thích của tôi xin vui lòng cho tôi biết.
Shafik Yaghmour

1
Câu trả lời đầy đủ tốt đẹp - đặc biệt là chỉ dưới 0,5 phần. Một ngách khác : round(-0.0). C spec không xuất hiện để chỉ định. Tôi mong đợi -0.0như một kết quả.
chux - Tái lập Monica

3
@chux thú vị, và tiêu chuẩn IEEE 754-2008 chỉ định rằng làm tròn sẽ bảo tồn các dấu hiệu của số không và số nguyên (xem 5.9).
Ruslan

1
@Shafik đây là một câu trả lời tuyệt vời. Tôi chưa bao giờ nghĩ rằng làm tròn thậm chí là một hoạt động không tầm thường.
Ruslan

1
Có lẽ đáng nói đến std::rint()thường được ưa thích hơn std::round()khi C ++ 11 có sẵn vì lý do số và hiệu suất. Nó sử dụng chế độ làm tròn hiện tại, không giống như round()chế độ đặc biệt. Nó có thể hiệu quả hơn nhiều trên x86, trong đó rintcó thể nội tuyến theo một lệnh đơn. (gcc và -ffast-math clang làm điều đó ngay cả khi không có godbolt.org/g/5UsL2e , trong khi chỉ có tiếng kêu gần như tương đương nearbyint()) ARM có hỗ trợ chỉ dẫn đơn round(), nhưng trên x86, nó chỉ có thể nội tuyến với nhiều hướng dẫn và chỉ với-ffast-math
Peter Cordes

71

Có thể đáng lưu ý rằng nếu bạn muốn có một kết quả số nguyên từ làm tròn, bạn không cần phải chuyển nó qua trần hoặc sàn. I E,

int round_int( double r ) {
    return (r > 0.0) ? (r + 0.5) : (r - 0.5); 
}

3
Mặc dù không mang lại kết quả như mong đợi cho 0.49999999999999994 (tốt, tùy thuộc vào những gì bạn mong đợi, nhưng 0 có vẻ hợp lý hơn với tôi hơn 1)
stijn 27/11/13

@stijn Bắt tốt. Tôi thấy rằng việc thêm hậu tố hai chữ dài vào các hằng số của tôi đã khắc phục vấn đề ví dụ của bạn, nhưng tôi không biết liệu có những ví dụ chính xác nào khác mà nó sẽ không bắt được không.
kal Wax

1
btw nếu bạn thêm 0.49999999999999994 thay vì 0.5, nó hoạt động tốt cho cả 0.49999999999999994 và 5000000000000001.0 làm đầu vào. Không chắc chắn liệu nó có ổn cho tất cả các giá trị hay không và tôi không thể tìm thấy bất kỳ tham chiếu nào nói rằng đây là bản sửa lỗi cuối cùng.
stijn

1
@stijn Mọi thứ đều ổn, nếu bạn không quan tâm theo hướng nào thì các giá trị nằm chính xác giữa hai số nguyên được làm tròn. Không cần suy nghĩ, tôi sẽ chứng minh bằng phân tích trường hợp với các trường hợp sau: 0 <= d <0,5, 0,5 <= d <1,5, 1,5 <= d <2 ^ 52, d> = 2 ^ 52. Tôi cũng đã kiểm tra toàn diện trường hợp chính xác duy nhất.
Pascal Cuoq

3
Trên 4,9 [conv.fpint], "Hành vi không được xác định nếu giá trị bị cắt không thể được biểu diễn trong loại đích." , vì vậy điều này là một chút nguy hiểm. Các câu trả lời SO khác mô tả cách thực hiện điều này một cách mạnh mẽ.
Tony Delroy

41

Nó có sẵn kể từ C ++ 11 tính bằng cmath (theo http://www.open-std.org/jtc1/sc22/wg21/docs/ con / 2012 / n3337.pdf )

#include <cmath>
#include <iostream>

int main(int argc, char** argv) {
  std::cout << "round(0.5):\t" << round(0.5) << std::endl;
  std::cout << "round(-0.5):\t" << round(-0.5) << std::endl;
  std::cout << "round(1.4):\t" << round(1.4) << std::endl;
  std::cout << "round(-1.4):\t" << round(-1.4) << std::endl;
  std::cout << "round(1.6):\t" << round(1.6) << std::endl;
  std::cout << "round(-1.6):\t" << round(-1.6) << std::endl;
  return 0;
}

Đầu ra:

round(0.5):  1
round(-0.5): -1
round(1.4):  1
round(-1.4): -1
round(1.6):  2
round(-1.6): -2

1
cũng có lroundllroundcho kết quả không thể thiếu
sp2danny

@ sp2danny: hoặc tốt hơn, lrintđể sử dụng chế độ làm tròn hiện tại thay vì kết hợp chặt chẽ roundtừ 0 đến 0.
Peter Cordes

27

Nó thường được thực hiện như là floor(value + 0.5) .

Chỉnh sửa: và có lẽ nó không được gọi là tròn vì có ít nhất ba thuật toán làm tròn mà tôi biết: làm tròn thành số không, làm tròn đến số nguyên gần nhất và làm tròn số của ngân hàng. Bạn đang yêu cầu làm tròn đến số nguyên gần nhất.


1
Thật tốt khi phân biệt giữa các phiên bản khác nhau của 'vòng'. Thật tốt khi biết khi nào cũng nên chọn.
xtofl

5
Thực sự có các thuật toán làm tròn khác nhau, tất cả đều có thể đưa ra tuyên bố hợp lý là "chính xác". Tuy nhiên, sàn (giá trị + 0,5) không phải là một trong số này. Đối với một số giá trị, chẳng hạn như 0,49999997f hoặc gấp đôi tương đương, câu trả lời là sai - nó sẽ được làm tròn thành 1.0 khi tất cả đồng ý rằng nó phải bằng không. Xem bài đăng này để biết chi tiết: blog.frama-c.com/index.php?post/2013/05/02/gầnbyintf1
Bruce Dawson

14

Có 2 vấn đề chúng tôi đang xem xét:

  1. chuyển đổi làm tròn
  2. chuyển đổi loại.

Chuyển đổi làm tròn có nghĩa là làm tròn ± float / double đến sàn gần nhất / trần nổi / double. Có thể vấn đề của bạn kết thúc ở đây. Nhưng nếu bạn dự kiến ​​sẽ trả lại Int / Long, bạn cần thực hiện chuyển đổi loại và do đó, vấn đề "Tràn" có thể ảnh hưởng đến giải pháp của bạn. VÌ VẬY, hãy kiểm tra lỗi trong chức năng của bạn

long round(double x) {
   assert(x >= LONG_MIN-0.5);
   assert(x <= LONG_MAX+0.5);
   if (x >= 0)
      return (long) (x+0.5);
   return (long) (x-0.5);
}

#define round(x) ((x) < LONG_MIN-0.5 || (x) > LONG_MAX+0.5 ?\
      error() : ((x)>=0?(long)((x)+0.5):(long)((x)-0.5))

từ: http://www.cs.tut.fi/~jkorpela/round.html


Sử dụng LONG_MIN-0.5LONG_MAX+0.5 giới thiệu các biến chứng như toán học có thể không chính xác. LONG_MAXcó thể vượt quá doubleđộ chính xác để chuyển đổi chính xác. Hơn nữa có khả năng muốn assert(x < LONG_MAX+0.5); (<vs <=) LONG_MAX+0.5có thể chính xác đại diện và (x)+0.5có thể có kết quả chính xác trong LONG_MAX+1đó không thành công long. Các vấn đề góc khác cũng vậy.
chux - Phục hồi Monica

Đừng gọi hàm của bạn round(double), đã có hàm thư viện toán học tiêu chuẩn của tên đó (trong C ++ 11) nên thật khó hiểu. Sử dụng std::lrint(x)nếu nó có sẵn.
Peter Cordes

11

Một kiểu làm tròn nhất định cũng được triển khai trong Boost:

#include <iostream>

#include <boost/numeric/conversion/converter.hpp>

template<typename T, typename S> T round2(const S& x) {
  typedef boost::numeric::conversion_traits<T, S> Traits;
  typedef boost::numeric::def_overflow_handler OverflowHandler;
  typedef boost::numeric::RoundEven<typename Traits::source_type> Rounder;
  typedef boost::numeric::converter<T, S, Traits, OverflowHandler, Rounder> Converter;
  return Converter::convert(x);
}

int main() {
  std::cout << round2<int, double>(0.1) << ' ' << round2<int, double>(-0.1) << ' ' << round2<int, double>(-0.9) << std::endl;
}

Lưu ý rằng điều này chỉ hoạt động nếu bạn thực hiện chuyển đổi thành số nguyên.


2
Boost cũng cung cấp một tập hợp các chức năng làm tròn đơn giản; xem câu trả lời của tôi
Daniel Wolf

Bạn cũng có thể sử dụng boost:numeric::RoundEven< double >::nearbyinttrực tiếp nếu bạn không muốn số nguyên. @DanielWolf lưu ý rằng chức năng đơn giản được triển khai bằng cách sử dụng +0,5 có vấn đề như được đặt ra bởi aka.nice
stijn

6

Bạn có thể làm tròn đến n chữ số chính xác với:

double round( double x )
{
const double sd = 1000; //for accuracy to 3 decimal places
return int(x*sd + (x<0? -0.5 : 0.5))/sd;
}

4
Trừ khi trình biên dịch kích thước int của bạn mặc định là 1024 bit, điều này sẽ không chính xác cho gấp đôi lớn ...
aka.nice

Tôi nghĩ rằng điều đó có thể chấp nhận được khi nó sẽ được sử dụng: Nếu giá trị kép của bạn là 1,0 e + 19, làm tròn đến 3 vị trí không có ý nghĩa.
Carl

3
chắc chắn, nhưng câu hỏi dành cho một vòng chung và bạn không thể kiểm soát cách sử dụng nó. Không có lý do cho vòng để thất bại trong đó trần và sàn sẽ không.
aka.nice

Điều này có hành vi không xác định cho các đối số bên ngoài phạm vi int. (Trong thực tế trên x86, các giá trị FP ngoài phạm vi sẽ tạo CVTTSD2SIra sản phẩm0x80000000 dưới dạng mẫu bit nguyên, nghĩa là INT_MINsau đó sẽ được chuyển đổi trở lại double.
Peter Cordes

5

Ngày nay, việc sử dụng trình biên dịch C ++ 11 bao gồm thư viện toán học C99 / C ++ 11 không phải là vấn đề. Nhưng sau đó, câu hỏi trở thành: bạn chọn chức năng làm tròn nào?

C99 / C ++ 11 round()thường không thực sự là chức năng làm tròn mà bạn muốn . Nó sử dụng chế độ làm tròn thú vị làm tròn từ 0 dưới dạng tie-break trong các trường hợp nửa đường ( +-xxx.5000). Nếu bạn đặc biệt muốn chế độ làm tròn đó hoặc bạn đang nhắm mục tiêu triển khai C ++ ở nơi round()nhanh hơn rint(), thì hãy sử dụng nó (hoặc mô phỏng hành vi của nó với một trong những câu trả lời khác cho câu hỏi này theo mệnh giá và sao chép cẩn thận cụ thể hành vi làm tròn.)

round()Làm tròn số khác với vòng mặc định của IEEE754 đến chế độ gần nhất ngay cả khi kết thúc . Gần nhất - thậm chí tránh sai lệch thống kê về độ lớn trung bình của các số, nhưng không thiên vị đối với các số chẵn.

Có hai hàm làm tròn thư viện toán học sử dụng chế độ làm tròn mặc định hiện tại: std::nearbyint()std::rint()cả hai được thêm vào trong C99 / C ++ 11, vì vậy chúng có sẵn bất cứ lúc nào std::round(). Sự khác biệt duy nhất lànearbyint không bao giờ tăng FE_INEXACT.

Thích rint()vì lý do hiệu suất : gcc và clang cả nội tuyến dễ dàng hơn, nhưng gcc không bao giờ nội tuyến nearbyint()(ngay cả với -ffast-math)


gcc / clang cho x86-64 và AArch64

Tôi đặt một số chức năng kiểm tra trên Matt Godbolt's Compiler Explorer , nơi bạn có thể thấy đầu ra nguồn + asm (cho nhiều trình biên dịch). Để biết thêm về cách đọc đầu ra của trình biên dịch, hãy xem phần Hỏi & Đáp này và bài nói chuyện CppCon2017 của Matt: Trình biên dịch của tôi đã làm gì cho tôi gần đây? Mở khóa nắp máy tính toán ,

Trong mã FP, thường là một chiến thắng lớn cho các hàm nhỏ nội tuyến. Đặc biệt trên không phải Windows, nơi quy ước gọi tiêu chuẩn không có các thanh ghi được bảo toàn cuộc gọi, do đó trình biên dịch không thể giữ bất kỳ giá trị FP nào trong các thanh ghi XMM trên mộtcall . Vì vậy, ngay cả khi bạn không thực sự biết asm, bạn vẫn có thể dễ dàng xem liệu đó chỉ là một cuộc gọi đuôi đến chức năng thư viện hay liệu nó có phù hợp với một hoặc hai hướng dẫn toán học hay không. Bất cứ điều gì liên quan đến một hoặc hai hướng dẫn đều tốt hơn lệnh gọi hàm (đối với tác vụ cụ thể này trên x86 hoặc ARM).

Trên x86, mọi thứ liên quan đến SSE4.1 roundsdđều có thể tự động vector hóa với SSE4.1 roundpd(hoặc AVX vroundpd). (FP-> chuyển đổi số nguyên cũng có sẵn ở dạng SIMD được đóng gói, ngoại trừ số nguyên FP-> 64 bit yêu cầu AVX512.)

  • std::nearbyint():

    • x86 clang: nội tuyến đến một insn duy nhất với -msse4.1 .
    • x86 gcc: nội tuyến đến một insn duy nhất với -msse4.1 -ffast-mathvà chỉ trên gcc 5.4 trở về trước . Gcc sau này không bao giờ nội tuyến (có lẽ họ đã không nhận ra rằng một trong những bit ngay lập tức có thể triệt tiêu ngoại lệ không chính xác? Đó là những gì clang sử dụng, nhưng gcc cũ sử dụng ngay lập tức như rintkhi nó thực hiện nội tuyến)
    • Theo mặc định, AArch64 gcc6.3: nội tuyến đến một insn duy nhất.
  • std::rint:

    • x86 clang: nội tuyến đến một insn duy nhất với -msse4.1
    • x86 gcc7: nội tuyến cho một insn với -msse4.1. (Không có SSE4.1, hãy hướng dẫn một số hướng dẫn)
    • x86 gcc6.x trở về trước: nội tuyến vào một insn duy nhất với -ffast-math -msse4.1.
    • Theo mặc định, AArch64 gcc: inline đến một insn
  • std::round:

    • x86 clang: không nội tuyến
    • x86 gcc: nội tuyến theo nhiều hướng dẫn với -ffast-math -msse4.1, yêu cầu hai hằng số vectơ.
    • AArch64 gcc: nội tuyến theo một hướng dẫn duy nhất (hỗ trợ CTNH cho chế độ làm tròn này cũng như mặc định của IEEE và hầu hết các hướng dẫn khác.)
  • std::floor/ std::ceil/std::trunc

    • x86 clang: nội tuyến đến một insn duy nhất với -msse4.1
    • x86 gcc7.x: nội tuyến vào một in -msse4.1
    • x86 gcc6.x trở về trước: nội tuyến trong một lần in -ffast-math -msse4.1
    • AArch64 gcc: nội tuyến theo mặc định cho một lệnh

Làm tròn đến int/ long/ long long:

Bạn có hai tùy chọn ở đây: sử dụng lrint(như rintnhưng trả về longhoặc long longcho llrint) hoặc sử dụng hàm làm tròn FP-> FP và sau đó chuyển đổi sang loại số nguyên theo cách thông thường (có cắt bớt). Một số trình biên dịch tối ưu hóa một cách tốt hơn so với cách khác.

long l = lrint(x);

int  i = (int)rint(x);

Lưu ý rằng int i = lrint(x)chuyển đổi floathoặc double-> longtrước, sau đó cắt số nguyên thànhint . Điều này tạo ra sự khác biệt cho các số nguyên ngoài phạm vi: Hành vi không xác định trong C ++, nhưng được xác định rõ cho các lệnh x86 FP -> int (trình biên dịch sẽ phát ra trừ khi nó nhìn thấy UB ở thời gian biên dịch trong khi truyền liên tục, sau đó được phép tạo mã bị phá vỡ nếu nó được thực thi).

Trên x86, một chuyển đổi số nguyên FP-> tràn số nguyên tạo ra INT_MINhoặc LLONG_MIN(một mẫu 0x8000000bit tương đương hoặc 64 bit, chỉ với tập bit-bit). Intel gọi đây là giá trị "số nguyên không xác định". (Xem các cvttsd2sientry của nhãn hiệu , chỉ dẫn SSE2 rằng cải đạo (với cắt ngắn) vô hướng đôi để nguyên ký kết. Nó có sẵn với 32-bit hoặc 64-bit điểm đến số nguyên (chỉ trong chế độ 64-bit). Ngoài ra còn có một cvtsd2si(chuyển đổi với làm tròn hiện tại chế độ), đó là những gì chúng tôi muốn trình biên dịch phát ra, nhưng tiếc là gcc và clang sẽ không làm điều đó mà không có -ffast-math.

Ngoài ra, hãy cẩn thận rằng FP đến / từ unsignedint / long sẽ kém hiệu quả hơn trên x86 (không có AVX512). Chuyển đổi sang 32 bit không dấu trên máy 64 bit khá rẻ; chỉ cần chuyển đổi thành 64-bit đã ký và cắt bớt. Nhưng nếu không thì nó chậm hơn đáng kể.

  • x86 clang có / không -ffast-math -msse4.1: (int/long)rintinline đến roundsd/ cvttsd2si. (bỏ lỡ tối ưu hóa cvtsd2si). lrinthoàn toàn không nội tuyến.

  • x86 gcc6.x trở về trước mà không có -ffast-math: không cách nào nội tuyến

  • x86 gcc7 không có -ffast-math: làm (int/long)rinttròn và chuyển đổi riêng biệt (với 2 hướng dẫn tổng thể của SSE4.1 được bật, nếu không thì có một loạt mã được in rintmà không có roundsd). lrintkhông nội tuyến.
  • x86 gcc với -ffast-math : tất cả các cách nội tuyến cvtsd2si(tối ưu) , không cần SSE4.1.

  • AArch64 gcc6.3 không có -ffast-math: nội tuyến (int/long)rintđến 2 hướng dẫn. lrintkhông nội tuyến

  • AArch64 gcc6.3 với -ffast-math: (int/long)rintbiên dịch thành một cuộc gọi đến lrint. lrintkhông nội tuyến. Đây có thể là một tối ưu hóa bị bỏ qua trừ khi hai hướng dẫn chúng tôi nhận được mà không -ffast-mathquá chậm.

TODO: ICC và MSVC cũng có sẵn trên Godbolt, nhưng tôi đã không nhìn vào đầu ra của chúng cho việc này. chỉnh sửa chào mừng ... Ngoài ra: nó sẽ hữu ích hơn khi phân tích bằng trình biên dịch / phiên bản trước và sau đó bởi chức năng trong đó? Hầu hết mọi người sẽ không chuyển đổi trình biên dịch dựa trên mức độ họ biên dịch FP-> FP hoặc FP-> làm tròn số nguyên.
Peter Cordes

2
+1 để đề xuất rint()nơi đó là một lựa chọn khả thi, thường là trường hợp. Tôi đoán cái tên round()ám chỉ một số lập trình viên rằng đây là điều họ muốn, trong khi rint()có vẻ bí ẩn. Lưu ý rằng round()không sử dụng chế độ làm tròn "vui nhộn": làm tròn từ gần đến gần nhất là chế độ làm tròn chính thức của IEEE-754 (2008). Thật tò mò rằng nearbyint()nó không được nội tuyến, cho rằng nó phần lớn giống như rint(), và nên giống hệt nhau trong các -ffast-mathđiều kiện. Điều đó có vẻ như lỗi với tôi.
njuffa

4

Coi chừng floor(x+0.5). Đây là những gì có thể xảy ra đối với các số lẻ trong phạm vi [2 ^ 52,2 ^ 53]:

-bash-3.2$ cat >test-round.c <<END

#include <math.h>
#include <stdio.h>

int main() {
    double x=5000000000000001.0;
    double y=round(x);
    double z=floor(x+0.5);
    printf("      x     =%f\n",x);
    printf("round(x)    =%f\n",y);
    printf("floor(x+0.5)=%f\n",z);
    return 0;
}
END

-bash-3.2$ gcc test-round.c
-bash-3.2$ ./a.out
      x     =5000000000000001.000000
round(x)    =5000000000000001.000000
floor(x+0.5)=5000000000000002.000000

Đây là http://bugs.squeak.org/view.php?id=7134 . Sử dụng một giải pháp như một trong những @konik.

Phiên bản mạnh mẽ của riêng tôi sẽ là một cái gì đó như:

double round(double x)
{
    double truncated,roundedFraction;
    double fraction = modf(x, &truncated);
    modf(2.0*fraction, &roundedFraction);
    return truncated + roundedFraction;
}

Một lý do khác để tránh sàn (x + 0,5) được đưa ra ở đây .


2
Tôi quan tâm để biết về các downvote. Có phải bởi vì cà vạt được giải quyết từ số 0 chứ không phải gần nhất?
aka.nice

1
Lưu ý: thông số kỹ thuật C cho biết "làm tròn nửa trường hợp cách xa 0, bất kể hướng làm tròn hiện tại.", Vì vậy làm tròn mà không liên quan đến lẻ ​​/ chẵn là tuân thủ.
chux - Tái lập Monica

4

Nếu cuối cùng bạn muốn chuyển đổi doubleđầu ra của round()hàm thành một int, thì các giải pháp được chấp nhận của câu hỏi này sẽ trông giống như:

int roundint(double r) {
  return (int)((r > 0.0) ? floor(r + 0.5) : ceil(r - 0.5));
}

Đồng hồ này ở khoảng 8,88 ns trên máy của tôi khi được truyền các giá trị ngẫu nhiên đồng đều.

Dưới đây là chức năng tương đương, theo như tôi có thể nói, nhưng đồng hồ ở tốc độ 2,48 ns trên máy của tôi, cho một lợi thế đáng kể về hiệu suất:

int roundint (double r) {
  int tmp = static_cast<int> (r);
  tmp += (r-tmp>=.5) - (r-tmp<=-.5);
  return tmp;
}

Trong số các lý do cho hiệu suất tốt hơn là phân nhánh bị bỏ qua.


Điều này có hành vi không xác định cho các đối số bên ngoài phạm vi int. (Trong thực tế trên x86, các giá trị FP ngoài phạm vi sẽ tạo CVTTSD2SIra sản phẩm0x80000000 dưới dạng mẫu bit nguyên, nghĩa là INT_MINsau đó sẽ được chuyển đổi trở lại double.
Peter Cordes

2

Không cần phải thực hiện bất cứ điều gì, vì vậy tôi không chắc tại sao có quá nhiều câu trả lời liên quan đến định nghĩa, hàm hoặc phương thức.

Trong C99

Chúng tôi có các tiêu đề và tiêu đề <tgmath.h> sau đây cho các macro chung loại.

#include <math.h>
double round (double x);
float roundf (float x);
long double roundl (long double x);

Nếu bạn không thể biên dịch cái này, có lẽ bạn đã rời khỏi thư viện toán học. Một lệnh tương tự như thế này hoạt động trên mọi trình biên dịch C tôi có (một số).

gcc -lm -std=c99 ...

Trong C ++ 11

Chúng tôi có các tình trạng quá tải sau và bổ sung trong #include <cmath> dựa trên điểm nổi chính xác kép của IEEE.

#include <math.h>
double round (double x);
float round (float x);
long double round (long double x);
double round (T x);

tương đương trong không gian tên std quá.

Nếu bạn không thể biên dịch cái này, bạn có thể đang sử dụng trình biên dịch C thay vì C ++. Lệnh cơ bản sau đây không tạo ra lỗi cũng như cảnh báo với g ++ 6.3.1, x86_64-w64-mingw32-g ++ 6.3.0, clang-x86_64 ++ 3.8.0 và Cộng đồng Visual C ++ 2015.

g++ -std=c++11 -Wall

Với bộ phận thông thường

Khi chia hai số thứ tự, trong đó T là ngắn, int, dài hoặc một số thứ tự khác, biểu thức làm tròn là đây.

T roundedQuotient = (2 * integerNumerator + 1)
    / (2 * integerDenominator);

Sự chính xác

Không có nghi ngờ rằng sự không chính xác trông kỳ quặc xuất hiện trong các phép toán dấu phẩy động, nhưng điều này chỉ xảy ra khi các con số xuất hiện và ít liên quan đến làm tròn.

Nguồn không chỉ là số chữ số có nghĩa trong phần biểu thị của số của dấu phẩy động, nó có liên quan đến suy nghĩ thập phân của chúng ta là con người.

Mười là sản phẩm của năm và hai, và 5 và 2 là tương đối chính. Do đó, các tiêu chuẩn dấu phẩy động của IEEE có thể có thể được biểu diễn hoàn hảo dưới dạng số thập phân cho tất cả các biểu diễn kỹ thuật số nhị phân.

Đây không phải là một vấn đề với các thuật toán làm tròn. Đó là thực tế toán học cần được xem xét trong quá trình lựa chọn các loại và thiết kế tính toán, nhập dữ liệu và hiển thị số. Nếu một ứng dụng hiển thị các chữ số hiển thị các vấn đề chuyển đổi nhị phân thập phân này, thì ứng dụng đó thể hiện trực quan độ chính xác không tồn tại trong thực tế kỹ thuật số và nên được thay đổi.


1
"Tôi không chắc tại sao rất nhiều câu trả lời liên quan đến định nghĩa, hàm hoặc phương thức." Hãy xem khi nó được hỏi - C ++ 11 chưa ra mắt. ;)
jaggedSpire

@jaggedSpire, vậy thì hãy cho tôi biết, nếu bạn cảm thấy nó phù hợp, bởi vì tất cả các câu trả lời có điểm cao đều lỗi thời và sai lệch trong bối cảnh các trình biên dịch được sử dụng phổ biến nhất hiện nay.
FauChristian

2

Chức năng double round(double)với việc sử dụng modfchức năng:

double round(double x)
{
    using namespace std;

    if ((numeric_limits<double>::max() - 0.5) <= x)
        return numeric_limits<double>::max();

    if ((-1*std::numeric_limits<double>::max() + 0.5) > x)
        return (-1*std::numeric_limits<double>::max());

    double intpart;
    double fractpart = modf(x, &intpart);

    if (fractpart >= 0.5)
        return (intpart + 1);
    else if (fractpart >= -0.5)
        return intpart;
    else
        return (intpart - 1);
    }

Để được biên dịch sạch, bao gồm "math.h" và "giới hạn" là cần thiết. Hàm này hoạt động theo một lược đồ làm tròn sau:

  • vòng 5.0 là 5.0
  • vòng 3,8 là 4,0
  • vòng 2,3 là 2,0
  • vòng 1,5 là 2,0
  • vòng 0,501 là 1,0
  • vòng 0,5 là 1,0
  • vòng 0,499 là 0,0
  • vòng 0,01 là 0,0
  • vòng 0,0 là 0,0
  • vòng -0,01 là -0,0
  • vòng -0.499 là -0.0
  • vòng -0,5 là -0,0
  • vòng -0,501 là -1,0
  • vòng -1,5 là -1,0
  • vòng -2,3 là -2,0
  • vòng -3,8 là -4.0
  • vòng -5.0 là -5.0

2
Đây là một giải pháp tốt. Mặc dù vậy, tôi không chắc chắn rằng làm tròn -1,5 đến -1,0 là tiêu chuẩn, tôi sẽ mong đợi -2.0 theo phép đo. Ngoài ra tôi không thấy quan điểm của người bảo vệ hàng đầu, hai người đầu tiên nếu có thể được gỡ bỏ.
aka.nice

2
Tôi đã kiểm tra theo tiêu chuẩn ISO / IEC 10967-2, open-std.org/jtc1/sc22/wg11/docs/n462.pdf và từ phụ lục B.5.2.4, chức năng làm tròn thực sự phải đối xứng, làm tròn số (x) = neg_F (rounding_F (neg_F (x)))
aka.nice

Điều này sẽ chậm so với C ++ 11 rint()hoặc nearbyint(), nhưng nếu bạn thực sự không thể sử dụng trình biên dịch cung cấp chức năng làm tròn phù hợp và bạn cần độ chính xác cao hơn hiệu suất ...
Peter Cordes

1

Nếu bạn cần có khả năng biên dịch mã trong các môi trường hỗ trợ chuẩn C ++ 11, nhưng cũng cần có khả năng biên dịch cùng mã đó trong các môi trường không hỗ trợ nó, bạn có thể sử dụng macro chức năng để chọn giữa std :: round () và một chức năng tùy chỉnh cho mỗi hệ thống. Chỉ cần chuyển -DCPP11hoặc /DCPP11đến trình biên dịch tương thích C ++ 11 (hoặc sử dụng macro phiên bản dựng sẵn của nó) và tạo một tiêu đề như thế này:

// File: rounding.h
#include <cmath>

#ifdef CPP11
    #define ROUND(x) std::round(x)
#else    /* CPP11 */
    inline double myRound(double x) {
        return (x >= 0.0 ? std::floor(x + 0.5) : std::ceil(x - 0.5));
    }

    #define ROUND(x) myRound(x)
#endif   /* CPP11 */

Để biết ví dụ nhanh, hãy xem http://ideone.com/zal709 .

Điều này gần đúng std :: round () trong các môi trường không tuân thủ C ++ 11, bao gồm bảo toàn bit dấu cho -0.0. Tuy nhiên, điều này có thể gây ra một cú đánh hiệu suất nhẹ và có thể sẽ gặp sự cố với làm tròn các giá trị dấu phẩy động "có vấn đề" nhất định như 0.49999999999999994 hoặc các giá trị tương tự.

Ngoài ra, nếu bạn có quyền truy cập vào trình biên dịch tương thích C ++ 11, bạn có thể lấy std :: round () từ <cmath>tiêu đề của nó và sử dụng nó để tạo tiêu đề của riêng bạn xác định hàm nếu nó chưa được xác định. Lưu ý rằng đây có thể không phải là một giải pháp tối ưu, tuy nhiên, đặc biệt nếu bạn cần biên dịch cho nhiều nền tảng.


1

Dựa trên phản hồi của Kal Wax, sau đây là một giải pháp khuôn mẫu làm tròn bất kỳ số dấu phẩy động nào thành loại số nguyên gần nhất dựa trên làm tròn tự nhiên. Nó cũng đưa ra một lỗi trong chế độ gỡ lỗi nếu giá trị nằm ngoài phạm vi của kiểu số nguyên, do đó phục vụ gần như là một hàm thư viện khả thi.

    // round a floating point number to the nearest integer
    template <typename Arg>
    int Round(Arg arg)
    {
#ifndef NDEBUG
        // check that the argument can be rounded given the return type:
        if (
            (Arg)std::numeric_limits<int>::max() < arg + (Arg) 0.5) ||
            (Arg)std::numeric_limits<int>::lowest() > arg - (Arg) 0.5)
            )
        {
            throw std::overflow_error("out of bounds");
        }
#endif

        return (arg > (Arg) 0.0) ? (int)(r + (Arg) 0.5) : (int)(r - (Arg) 0.5);
    }

1
Như tôi đã chỉ ra trong câu trả lời của mình, việc thêm vào 0.5không hoạt động trong mọi trường hợp. Mặc dù ít nhất bạn xử lý vấn đề tràn để bạn tránh hành vi không xác định.
Shafik Yaghmour

1

Như đã chỉ ra trong các bình luận và các câu trả lời khác, thư viện tiêu chuẩn ISO C ++ đã không thêm vào round()cho đến ISO C ++ 11, khi chức năng này được kéo vào bằng cách tham chiếu đến thư viện toán học tiêu chuẩn ISO C99.

Đối với toán hạng tích cực trong [½, ub ] round(x) == floor (x + 0.5), nơi ub là 2 23 cho floatkhi ánh xạ tới IEEE-754 (2008) binary32, và 2 52 cho doublekhi nó được ánh xạ tới IEEE-754 (2008) binary64. Các số 23 và 52 tương ứng với số bit mantissa được lưu trữ trong hai định dạng dấu phẩy động này. Đối với các toán hạng dương trong [+0, ½) round(x) == 0và đối với các toán hạng dương trong ( ub , +] round(x) == x. Vì hàm này đối xứng với trục x, các đối số phủ định xcó thể được xử lý theo round(-x) == -round(x).

Điều này dẫn đến mã nhỏ gọn dưới đây. Nó biên dịch thành một số lượng hợp lý các hướng dẫn máy trên các nền tảng khác nhau. Tôi đã quan sát mã nhỏ gọn nhất trên GPU, trong đó my_roundf()yêu cầu khoảng một chục hướng dẫn. Tùy thuộc vào kiến ​​trúc bộ xử lý và chuỗi công cụ, cách tiếp cận dựa trên dấu phẩy động này có thể nhanh hơn hoặc chậm hơn so với triển khai dựa trên số nguyên từ newlib được tham chiếu trong một câu trả lời khác nhau .

Tôi đã kiểm tra my_roundf()toàn diện đối với việc roundf()triển khai newlib bằng trình biên dịch Intel phiên bản 13, với cả /fp:strict/fp:fast. Tôi cũng đã kiểm tra rằng phiên bản newlib phù hợp với roundf()trong mathimfthư viện của trình biên dịch của Intel. Kiểm tra toàn diện là không thể đối với độ chính xác kép round(), tuy nhiên mã này có cấu trúc giống hệt với triển khai độ chính xác đơn.

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <math.h>

float my_roundf (float x)
{
    const float half = 0.5f;
    const float one = 2 * half;
    const float lbound = half;
    const float ubound = 1L << 23;
    float a, f, r, s, t;
    s = (x < 0) ? (-one) : one;
    a = x * s;
    t = (a < lbound) ? x : s;
    f = (a < lbound) ? 0 : floorf (a + half);
    r = (a > ubound) ? x : (t * f);
    return r;
}

double my_round (double x)
{
    const double half = 0.5;
    const double one = 2 * half;
    const double lbound = half;
    const double ubound = 1ULL << 52;
    double a, f, r, s, t;
    s = (x < 0) ? (-one) : one;
    a = x * s;
    t = (a < lbound) ? x : s;
    f = (a < lbound) ? 0 : floor (a + half);
    r = (a > ubound) ? x : (t * f);
    return r;
}

uint32_t float_as_uint (float a)
{
    uint32_t r;
    memcpy (&r, &a, sizeof(r));
    return r;
}

float uint_as_float (uint32_t a)
{
    float r;
    memcpy (&r, &a, sizeof(r));
    return r;
}

float newlib_roundf (float x)
{
    uint32_t w;
    int exponent_less_127;

    w = float_as_uint(x);
    /* Extract exponent field. */
    exponent_less_127 = (int)((w & 0x7f800000) >> 23) - 127;
    if (exponent_less_127 < 23) {
        if (exponent_less_127 < 0) {
            /* Extract sign bit. */
            w &= 0x80000000;
            if (exponent_less_127 == -1) {
                /* Result is +1.0 or -1.0. */
                w |= ((uint32_t)127 << 23);
            }
        } else {
            uint32_t exponent_mask = 0x007fffff >> exponent_less_127;
            if ((w & exponent_mask) == 0) {
                /* x has an integral value. */
                return x;
            }
            w += 0x00400000 >> exponent_less_127;
            w &= ~exponent_mask;
        }
    } else {
        if (exponent_less_127 == 128) {
            /* x is NaN or infinite so raise FE_INVALID by adding */
            return x + x;
        } else {
            return x;
        }
    }
    x = uint_as_float (w);
    return x;
}

int main (void)
{
    uint32_t argi, resi, refi;
    float arg, res, ref;

    argi = 0;
    do {
        arg = uint_as_float (argi);
        ref = newlib_roundf (arg);
        res = my_roundf (arg);
        resi = float_as_uint (res);
        refi = float_as_uint (ref);
        if (resi != refi) { // check for identical bit pattern
            printf ("!!!! arg=%08x  res=%08x  ref=%08x\n", argi, resi, refi);
            return EXIT_FAILURE;
        }
        argi++;
    } while (argi);
    return EXIT_SUCCESS;
}

Tôi đã thực hiện một chỉnh sửa để tránh giả sử intrộng hơn 16 bit. Tất nhiên, nó vẫn cho rằng đó floatlà nhị phân 4 byte của IEEE754. C ++ 11 static_asserthoặc có thể là macro #ifdef/ #errorcó thể kiểm tra điều đó. (Nhưng tất nhiên nếu C ++ 11 khả dụng, bạn nên sử dụng std::roundhoặc cho chế độ làm tròn hiện tại, sử dụng std::rintđể phù hợp với gcc và clang).
Peter Cordes

BTW, gcc -ffast-math -msse4.1inlines std::round()một add( AND(x, L1), OR(x,L2), và sau đó một roundsd. tức là nó thực hiện khá hiệu quả roundvề mặt rint. Nhưng không có lý do để làm điều này bằng tay trong nguồn C ++, bởi vì nếu bạn có std::rint()hoặc std::nearbyint()bạn cũng có std::round(). Xem câu trả lời của tôi cho một liên kết godbolt và danh sách những gì phù hợp hoặc không với các phiên bản gcc / clang khác nhau.
Peter Cordes

@PeterCordes Tôi nhận thức rõ cách triển khai round()hiệu quả về mặt rint()(khi cái sau đang hoạt động ở chế độ từ tròn đến gần nhất hoặc thậm chí): Tôi đã triển khai điều đó cho thư viện toán học tiêu chuẩn CUDA. Tuy nhiên, câu hỏi này dường như hỏi làm thế nào để thực hiện round()với C ++ trước C ++ 11, do đó, rint()sẽ không có sẵn, chỉ floor()ceil().
njuffa

@PeterCordes Xin lỗi, tôi sai chính tả. round()có thể dễ dàng tổng hợp từ rint()trong vòng to-zero- mode, aka trunc(). Không nên trả lời trước khi cà phê đầu tiên.
njuffa

1
@PeterCordes Tôi đồng ý rằng có khả năng OP không cần hành vi làm tròn cụ thể của round(); hầu hết các lập trình viên chỉ đơn giản là không nhận thức được sự khác biệt giữa round()so rint()với từ tròn đến gần nhất, trong đó phần sau thường được cung cấp trực tiếp bởi phần cứng và do đó hiệu quả hơn; Tôi đã đánh vần điều đó trong Hướng dẫn lập trình CUDA để làm cho các lập trình viên nhận thức: "Cách được khuyến nghị để làm tròn một toán hạng dấu phẩy động chính xác đơn thành một số nguyên, với kết quả là một số dấu phẩy động chính xác duy nhất là rintf()không roundf()".
njuffa

0

Tôi sử dụng cách thực hiện vòng trong asm sau cho kiến ​​trúc x86 và MS VS cụ thể C ++:

__forceinline int Round(const double v)
{
    int r;
    __asm
    {
        FLD     v
        FISTP   r
        FWAIT
    };
    return r;
}

CẬP NHẬT: để trả về giá trị gấp đôi

__forceinline double dround(const double v)
{
    double r;
    __asm
    {
        FLD     v
        FRNDINT
        FSTP    r
        FWAIT
    };
    return r;
}

Đầu ra:

dround(0.1): 0.000000000000000
dround(-0.1): -0.000000000000000
dround(0.9): 1.000000000000000
dround(-0.9): -1.000000000000000
dround(1.1): 1.000000000000000
dround(-1.1): -1.000000000000000
dround(0.49999999999999994): 0.000000000000000
dround(-0.49999999999999994): -0.000000000000000
dround(0.5): 0.000000000000000
dround(-0.5): -0.000000000000000

Giá trị kết quả phải là giá trị dấu phẩy động với độ chính xác kép.
Truthseeker

@ Truthseeker: Vâng, tôi phải xem loại giá trị trả về bắt buộc. OK, xem "CẬP NHẬT".
Aleksey F.

Trình biên dịch sẽ hy vọng nội tuyến rint()hoặc nearbyint()theo lệnh SSE4.1 roundsdhoặc lệnh x87 frndint, sẽ nhanh hơn nhiều so với hai lần lưu trữ / tải lại các chuyến đi khứ hồi cần thiết để sử dụng mã asm nội tuyến này trên dữ liệu trong một thanh ghi. MSVC nội tuyến asm hút khá nhiều để gói các hướng dẫn đơn lẻ như frndintvì không có cách nào để có được đầu vào trong một thanh ghi. Sử dụng nó ở cuối hàm với kết quả st(0)có thể đáng tin cậy như một cách trả về đầu ra; rõ ràng là an toàn cho eaxsố nguyên, ngay cả khi nó nội tuyến hàm chứa mã asm.
Peter Cordes

@PeterCordes Tối ưu hóa hiện đại được chào đón. Tuy nhiên tôi không thể sử dụng SSE4.1 vì nó không tồn tại vào lúc đó. Mục đích của tôi là cung cấp việc triển khai tối thiểu vòng có thể hoạt động ngay cả trên các họ Intel P3 hoặc P4 cũ từ những năm 2000.
Aleksey F.

P3 thậm chí không có SSE2, vì vậy trình biên dịch sẽ sử dụng x87 cho doublevà do đó sẽ có thể frndinttự phát ra rint(). Nếu trình biên dịch của bạn đang sử dụng SSE2, việc chuyển doubletừ a đăng ký XMM sang x87 và trở lại có thể không có giá trị.
Peter Cordes

0

Cách tốt nhất để làm tròn một giá trị nổi theo số thập phân "n", như sau với thời gian O (1): -

Chúng ta phải làm tròn giá trị theo 3 vị trí, tức là n = 3. Vì vậy,

float a=47.8732355;
printf("%.3f",a);

-4
// Convert the float to a string
// We might use stringstream, but it looks like it truncates the float to only
//5 decimal points (maybe that's what you want anyway =P)

float MyFloat = 5.11133333311111333;
float NewConvertedFloat = 0.0;
string FirstString = " ";
string SecondString = " ";
stringstream ss (stringstream::in | stringstream::out);
ss << MyFloat;
FirstString = ss.str();

// Take out how ever many decimal places you want
// (this is a string it includes the point)
SecondString = FirstString.substr(0,5);
//whatever precision decimal place you want

// Convert it back to a float
stringstream(SecondString) >> NewConvertedFloat;
cout << NewConvertedFloat;
system("pause");

Nó có thể là một cách chuyển đổi bẩn không hiệu quả nhưng quái gì, nó hoạt động lol. Và nó tốt, bởi vì nó áp dụng cho phao thực tế. Không chỉ ảnh hưởng đến đầu ra trực quan.


Điều này rất không hiệu quả, và nó cũng cắt ngắn (bằng cách luôn loại bỏ các chữ số ở cuối) thay vì làm tròn đến gần nhất.
Peter Cordes

-6

Tôi đã làm điều này:

#include <cmath.h>

using namespace std;

double roundh(double number, int place){

    /* place = decimal point. Putting in 0 will make it round to whole
                              number. putting in 1 will round to the
                              tenths digit.
    */

    number *= 10^place;
    int istack = (int)floor(number);
    int out = number-istack;
    if (out < 0.5){
        floor(number);
        number /= 10^place;
        return number;
    }
    if (out > 0.4) {
        ceil(number);
        number /= 10^place;
        return number;
    }
}

3
Bạn không có nghĩa là pow (10, địa điểm) chứ không phải là toán tử nhị phân ^ ở vị trí 10 ^? 10 ^ 2 trên máy của tôi cho tôi 8 !! Tuy nhiên, trên Mac 10.7.4 và gcc của tôi, mã không hoạt động, trả về giá trị ban đầu.
Pete855217
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.