round() 用于 C++ 中的浮点数

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/485525/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 15:37:03  来源:igfitidea点击:

round() for float in C++

c++floating-pointrounding

提问by Roddy

I need a simple floating point rounding function, thus:

我需要一个简单的浮点舍入函数,因此:

double round(double);

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

I can find ceil()and floor()in the math.h - but not round().

我可以在 math.h 中找到ceil()floor()- 但不是round()

Is it present in the standard C++ library under another name, or is it missing??

它是否以其他名称存在于标准 C++ 库中,还是缺失?

采纳答案by Andreas Magnusson

There's no round() in the C++98 standard library. You can write one yourself though. The following is an implementation of round-half-up:

C++98 标准库中没有 round() 。不过你可以自己写一个。以下是四舍五入的实现:

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

The probable reason there is no round function in the C++98 standard library is that it can in fact be implemented in different ways. The above is one common way but there are others such as round-to-even, which is less biased and generally better if you're going to do a lot of rounding; it's a bit more complex to implement though.

C++98 标准库中没有圆函数的可能原因是它实际上可以以不同的方式实现。以上是一种常用方法,但还有其他方法,例如round-to-even,如果您要进行大量舍入,则偏差较小,通常会更好;不过实现起来有点复杂。

回答by Daniel Wolf

Boost offers a simple set of rounding functions.

Boost 提供了一组简单的舍入函数。

#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

For more information, see the Boost documentation.

有关更多信息,请参阅Boost 文档

Edit: Since C++11, there are std::round, std::lround, and std::llround.

编辑:既然C ++ 11,还有std::roundstd::lroundstd::llround

回答by Shafik Yaghmour

The C++03 standard relies on the C90 standard for what the standard calls the Standard C Librarywhich is covered in the draft C++03 standard (closest publicly available draft standard to C++03 is N1804) section 1.2Normative references:

C++03 标准依赖于 C90 标准,该标准称为标准 C 库,该标准包含在 C++03 标准草案(与 C++03最接近的公开可用草案标准是 N1804)部分1.2规范参考

The library described in clause 7 of ISO/IEC 9899:1990 and clause 7 of ISO/IEC 9899/Amd.1:1995 is hereinafter called the Standard C Library.1)

ISO/IEC 9899:1990 第 7 条和 ISO/IEC 9899/Amd.1:1995 第 7 条中描述的库在下文中称为标准 C 库。1)

If we go to the C documentation for round, lround, llround on cppreferencewe can see that roundand related functions are part of C99and thus won't be available in C++03 or prior.

如果我们转到cppreference 上关于 round、lround、llroundC 文档,我们可以看到round和相关函数是C99 的一部分,因此在 C++03 或更早版本中不可用。

In C++11 this changes since C++11 relies on the C99 draft standard for C standard libraryand therefore provides std::round and for integral return types std::lround, std::llround:

在 C++11 中,这发生了变化,因为 C++11 依赖于 C99标准库的 C99 草案标准,因此提供std::round 和整数返回类型 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 ;
}

Another option also from C99 would be std::truncwhich:

同样来自 C99 的另一个选项是std::trunc,它:

Computes nearest integer not greater in magnitude than arg.

计算大小不大于 arg 的最近整数。

#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 ;

}

If you need to support non C++11 applications your best bet would be to use boost round, iround, lround, llroundor boost trunc.

如果您需要支持非 C++11 应用程序,最好的办法是使用boost round、iround、lround、llroundboost trunc

Rolling your own version of round is hard

滚动你自己的圆形版本很难

Rolling your own is probably not worth the effort as Harder than it looks: rounding float to nearest integer, part 1, Rounding float to nearest integer, part 2and Rounding float to nearest integer, part 3explain:

滚动你自己的可能不值得付出努力,因为它比看起来更难:将浮点数舍入到最接近的整数,第 1 部分,将浮点数舍入到最接近的整数,第 2 部分和将浮点数舍入到最接近的整数,第 3 部分解释:

For example a common roll your implementation using std::floorand adding 0.5does not work for all inputs:

例如,您的实现使用std::floor和添加的通用滚动0.5不适用于所有输入:

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

One input this will fail for is 0.49999999999999994, (see it live).

这将失败的一个输入是0.49999999999999994,(实时查看)。

Another common implementation involves casting a floating point type to an integral type, which can invoke undefined behavior in the case where the integral part can not be represented in the destination type. We can see this from the draft C++ standard section 4.9Floating-integral conversionswhich says (emphasis mine):

另一种常见的实现涉及将浮点类型转换为整数类型,这可能会在整数部分无法在目标类型中表示的情况下调用未定义的行为。我们可以从 C++ 标准草案部分4.9Floating-integral conversions 中看到这一点,其中说(强调我的):

A prvalue of a floating point type can be converted to a prvalue of an integer type. The conversion truncates; that is, the fractional part is discarded. The behavior is undefined if the truncated value cannot be represented in the destination type.[...]

浮点类型的纯右值可以转换为整数类型的纯右值。转换截断;也就是说,小数部分被丢弃。如果截断的值无法在目标类型中表示,则行为未定义。[...]

For example:

例如:

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

Given std::numeric_limits<unsigned int>::max()is 4294967295then the following call:

给出的std::numeric_limits<unsigned int>::max()4294967295以下调用:

myround( 4294967296.5f ) 

will cause overflow, (see it live).

会导致溢出,(现场观看)。

We can see how difficult this really is by looking at this answer to Concise way to implement round() in C?which referencing newlibsversion of single precision float round. It is a very long function for something which seems simple. It seems unlikely that anyone without intimate knowledge of floating point implementations could correctly implement this function:

通过查看Concise way to implement round() in C? 其中引用了单精度浮点轮的newlibs版本。对于看似简单的事情来说,这是一个很长的函数。对浮点实现没有深入了解的人似乎不太可能正确实现这个函数:

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;
}

On the other hand if none of the other solutions are usable newlibcould potentially be an option since it is a well tested implementation.

另一方面,如果没有其他解决方案可用,则newlib可能是一个选项,因为它是一个经过良好测试的实现。

回答by kalaxy

It may be worth noting that if you wanted an integer result from the rounding you don't need to pass it through either ceil or floor. I.e.,

可能值得注意的是,如果您想要四舍五入的整数结果,则不需要通过 ceil 或 floor 传递它。IE,

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

回答by schibum

It's available since C++11 in cmath (according to http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf)

它从 cmath 中的 C++11 开始可用(根据http://www.open-std.org/jtc1/sc22/wg21/docs/papers/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;
}

Output:

输出:

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

回答by MSN

It's usually implemented as floor(value + 0.5).

它通常实现为floor(value + 0.5).

Edit: and it's probably not called round since there are at least three rounding algorithms I know of: round to zero, round to closest integer, and banker's rounding. You are asking for round to closest integer.

编辑:它可能不被称为舍入,因为我知道至少有三种舍入算法:舍入到零、舍入到最接近的整数和银行家舍入。您要求舍入到最接近的整数。

回答by Sangeet

There are 2 problems we are looking at:

我们正在研究两个问题:

  1. rounding conversions
  2. type conversion.
  1. 四舍五入转换
  2. 类型转换。

Rounding conversions mean rounding ± float/double to nearest floor/ceil float/double. May be your problem ends here. But if you are expected to return Int/Long, you need to perform type conversion, and thus "Overflow" problem might hit your solution. SO, do a check for error in your function

四舍五入转换意味着四舍五入 ± 浮点数/双精度数到最近的下限/上限浮点数/双精度数。可能你的问题到此为止。但是如果您期望返回 Int/Long,则需要执行类型转换,因此“溢出”问题可能会影响您的解决方案。所以,检查你的函数中的错误

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))

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

来自:http: //www.cs.tut.fi/~jkorpela/round.html

回答by Philipp

A certain type of rounding is also implemented in Boost:

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;
}

Note that this works only if you do a to-integer conversion.

请注意,这仅在您进行到整数转换时才有效。

回答by Carl

You could round to n digits precision with:

您可以使用以下方法四舍五入到 n 位精度:

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;
}

回答by Peter Cordes

These days it shouldn't be a problem to use a C++11 compiler which includes a C99/C++11 math library. But then the question becomes: which rounding function do you pick?

如今,使用包含 C99/C++11 数学库的 C++11 编译器应该不是问题。但接下来的问题就变成了:您选择哪个舍入函数?

C99/C++11 round()is often not actually the rounding function you want. It uses a funky rounding mode that rounds away from 0 as a tie-break on half-way cases (+-xxx.5000). If you do specifically want that rounding mode, or you're targeting a C++ implementation where round()is faster than rint(), then use it (or emulate its behaviour with one of the other answers on this question which took it at face value and carefully reproduced that specific rounding behaviour.)

C99/C++11round()实际上通常不是您想要的舍入函数。它使用一种时髦的舍入模式,从 0 舍入作为中途情况下的抢七局 ( +-xxx.5000)。如果您确实特别想要这种舍入模式,或者您的目标round()是比 快的 C++ 实现rint(),则使用它(或使用该问题的其他答案之一模拟其行为,该答案以表面价值并仔细复制该特定舍入行为。)

round()'s rounding is different from the IEEE754 default round to nearest mode with even as a tie-break. Nearest-even avoids statistical bias in the average magnitude of numbers, but does bias towards even numbers.

round()的舍入与 IEEE754 默认舍入到最近模式不同,甚至作为 tie-break。Nearest-even 避免了数字平均大小的统计偏差,但会偏向偶数。

There are two math library rounding functions that use the current default rounding mode: std::nearbyint()and std::rint(), both added in C99/C++11, so they're available any time std::round()is. The only difference is that nearbyintnever raises FE_INEXACT.

有两个数学库舍入函数使用当前的默认舍入模式:std::nearbyint()and std::rint(),它们都是在 C99/C++11 中添加的,因此它们随时可用std::round()。唯一的区别是nearbyint从不引发 FE_INEXACT。

Prefer rint()for performance reasons: gcc and clang both inline it more easily, but gcc never inlines nearbyint()(even with -ffast-math)

rint()出于性能原因更喜欢:gcc 和 clang 都更容易内联它,但 gcc 从不内联nearbyint()(即使有-ffast-math



gcc/clang for x86-64 and AArch64

x86-64 和 AArch64 的 gcc/clang

I put some test functions on Matt Godbolt's Compiler Explorer, where you can see source + asm output (for multiple compilers). For more about reading compiler output, see this Q&A, and Matt's CppCon2017 talk: “What Has My Compiler Done for Me Lately? Unbolting the Compiler's Lid”,

在 Matt Godbolt 的 Compiler Explorer 上放了一些测试函数,在那里你可以看到 source + asm 输出(对于多个编译器)。有关阅读编译器输出的更多信息,请参阅此问答以及 Matt 的 CppCon2017 演讲:“我的编译器最近为我做了什么?打开编译器的盖子”

In FP code, it's usually a big win to inline small functions. Especially on non-Windows, where the standard calling convention has no call-preserved registers, so the compiler can't keep any FP values in XMM registers across a call. So even if you don't really know asm, you can still easily see whether it's just a tail-call to the library function or whether it inlined to one or two math instructions. Anything that inlines to one or two instructions is better than a function call (for this particular task on x86 or ARM).

在 FP 代码中,内联小函数通常是一个巨大的胜利。特别是在非 Windows 上,标准调用约定没有调用保留寄存器,因此编译器无法在call. 因此,即使您并不真正了解 asm,您仍然可以轻松查看它是否只是对库函数的尾调用,还是内联到一两个数学指令。任何内联到一两条指令的东西都比函数调用要好(对于 x86 或 ARM 上的这个特定任务)。

On x86, anything that inlines to SSE4.1 roundsdcan auto-vectorize with SSE4.1 roundpd(or AVX vroundpd). (FP->integer conversions are also available in packed SIMD form, except for FP->64-bit integer which requires AVX512.)

在 x86 上,任何内联到 SSE4.1 的东西都roundsd可以使用 SSE4.1 roundpd(或 AVX vroundpd)自动矢量化。(FP-> 整数转换也可以压缩 SIMD 形式使用,但需要 AVX512 的 FP->64 位整数除外。)

  • std::nearbyint():

    • x86 clang: inlines to a single insn with -msse4.1.
    • x86 gcc: inlines to a single insn only with -msse4.1 -ffast-math, and only on gcc 5.4 and earlier. Later gcc never inlines it (maybe they didn't realize that one of the immediate bits can suppress the inexact exception? That's what clang uses, but older gcc uses the same immediate as for rintwhen it does inline it)
    • AArch64 gcc6.3: inlines to a single insn by default.
  • std::rint:

    • x86 clang: inlines to a single insn with -msse4.1
    • x86 gcc7: inlines to a single insn with -msse4.1. (Without SSE4.1, inlines to several instructions)
    • x86 gcc6.x and earlier: inlines to a single insn with -ffast-math -msse4.1.
    • AArch64 gcc: inlines to a single insn by default
  • std::round:

    • x86 clang: doesn't inline
    • x86 gcc: inlines to multiple instructions with -ffast-math -msse4.1, requiring two vector constants.
    • AArch64 gcc: inlines to a single instruction (HW support for this rounding mode as well as IEEE default and most others.)
  • std::floor/ std::ceil/ std::trunc

    • x86 clang: inlines to a single insn with -msse4.1
    • x86 gcc7.x: inlines to a single insn with -msse4.1
    • x86 gcc6.x and earlier: inlines to a single insn with -ffast-math -msse4.1
    • AArch64 gcc: inlines by default to a single instruction
  • std::nearbyint()

    • x86 clang:内联到单个 insn -msse4.1
    • x86 gcc:-msse4.1 -ffast-math仅在 gcc 5.4 及更早版本上使用 内联到单个 insn 。后来 gcc 从不内联它(也许他们没有意识到直接位之一可以抑制不精确的异常?这就是 clang 使用的,但较旧的 gcc 使用与rint内联时相同的立即数)
    • AArch64 gcc6.3:默认内联到单个 insn。
  • std::rint

    • x86 clang:内联到单个 insn -msse4.1
    • x86 gcc7:内联到单个 insn 带有-msse4.1. (没有 SSE4.1,内联到几个指令)
    • x86 gcc6.x 及更早版本:内联到单个 insn 并带有-ffast-math -msse4.1.
    • AArch64 gcc:默认内联到单个 insn
  • std::round

    • x86 clang:不内联
    • x86 gcc:使用 内联到多条指令-ffast-math -msse4.1,需要两个向量常量。
    • AArch64 gcc:内联到单个指令(硬件支持这种舍入模式以及 IEEE 默认和大多数其他模式。)
  • std::floor/ std::ceil/std::trunc

    • x86 clang:内联到单个 insn -msse4.1
    • x86 gcc7.x:内联到单个 insn -msse4.1
    • x86 gcc6.x 及更早版本:内联到单个 insn -ffast-math -msse4.1
    • AArch64 gcc:默认内联到单个指令


Rounding to int/ long/ long long:

四舍五入到int/ long/ long long

You have two options here: use lrint(like rintbut returns long, or long longfor llrint), or use an FP->FP rounding function and then convert to an integer type the normal way (with truncation). Some compilers optimize one way better than the other.

您在这里有两个选择:使用lrint(例如rint但返回long,或long longfor llrint),或使用 FP->FP 舍入函数,然后以正常方式(使用截断)转换为整数类型。一些编译器以一种方式优化比另一种更好。

long l = lrint(x);

int  i = (int)rint(x);

Note that int i = lrint(x)converts floator double-> longfirst, and then truncates the integer to int. This makes a difference for out-of-range integers: Undefined Behaviour in C++, but well-defined for the x86 FP -> int instructions (which the compiler will emit unless it sees the UB at compile time while doing constant propagation, then it's allowed to make code that breaks if it's ever executed).

请注意,首先int i = lrint(x)转换floatdouble-> long,然后将整数截断为int。这对超出范围的整数有所不同:C++ 中的未定义行为,但为 x86 FP -> int 指令定义良好(编译器将发出该指令,除非它在编译时在进行常量传播时看到 UB,然后它是允许编写在执行时会中断的代码)。

On x86, an FP->integer conversion that overflows the integer produces INT_MINor LLONG_MIN(a bit-pattern of 0x8000000or the 64-bit equivalent, with just the sign-bit set). Intel calls this the "integer indefinite" value. (See the cvttsd2simanual entry, the SSE2 instruction that converts (with truncation) scalar double to signed integer. It's available with 32-bit or 64-bit integer destination (in 64-bit mode only). There's also a cvtsd2si(convert with current rounding mode), which is what we'd like the compiler to emit, but unfortunately gcc and clang won't do that without -ffast-math.

在 x86 上,溢出整数的 FP->integer 转换会产生INT_MINor LLONG_MIN0x8000000或 64 位等效的位模式,仅设置符号位)。英特尔将此称为“整数不定”值。(见cvttsd2si手动输入时,SSE2指令转换(与截断)标量双到符号整数,它是可用的32位或64位的整数的目的地(在仅64位模式)。也有一个cvtsd2si(转换与当前的舍入模式),这是我们希望编译器发出的,但不幸的是,如果没有-ffast-math.

Also beware that FP to/from unsignedint / long is less efficient on x86 (without AVX512). Conversion to 32-bit unsigned on a 64-bit machine is pretty cheap; just convert to 64-bit signed and truncate. But otherwise it's significantly slower.

还要注意 FP 到/从unsignedint/long 在 x86(没有 AVX512)上效率较低。在 64 位机器上转换为 32 位无符号非常便宜;只需转换为 64 位签名并截断即可。但否则它会明显变慢。

  • x86 clang with/without -ffast-math -msse4.1: (int/long)rintinlines to roundsd/ cvttsd2si. (missed optimization to cvtsd2si). lrintdoesn't inline at all.

  • x86 gcc6.x and earlier without -ffast-math: neither way inlines

  • x86 gcc7 without -ffast-math: (int/long)rintrounds and converts separately (with 2 total instructions of SSE4.1 is enabled, otherwise with a bunch of code inlined for rintwithout roundsd). lrintdoesn't inline.
  • x86 gcc with-ffast-math: all ways inline to cvtsd2si(optimal), no need for SSE4.1.

  • AArch64 gcc6.3 without -ffast-math: (int/long)rintinlines to 2 instructions. lrintdoesn't inline

  • AArch64 gcc6.3 with -ffast-math: (int/long)rintcompiles to a call to lrint. lrintdoesn't inline. This may be a missed optimization unless the two instructions we get without -ffast-mathare very slow.
  • x86 clang with/without -ffast-math -msse4.1: 内(int/long)rint联到roundsd/ cvttsd2si。(错过了优化cvtsd2si)。 lrint根本不内联。

  • x86 gcc6.x 及更早版本没有-ffast-math: 两种方式内联

  • x86 gcc7 without -ffast-math: 分别进行(int/long)rint四舍五入和转换(启用 SSE4.1 的总共 2 条指令,否则将内联一堆代码用于rint不带roundsd)。 lrint不内联。
  • x86 gcc with-ffast-math: all way inline to cvtsd2si(optimal),不需要SSE4.1。

  • AArch64 gcc6.3 没有-ffast-math: 内(int/long)rint联到 2 条指令。 lrint不内联

  • AArch64 gcc6.3 with -ffast-math:(int/long)rint编译为对lrint. lrint不内联。这可能是一个错过的优化,除非我们得到的两条指令-ffast-math非常慢。