round()for C++中的float

Rod*_*ddy 227 c++ floating-point rounding

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

double round(double);

round(0.1) = 0
round(-0.1) = 0
round(-0.9) = -1
Run Code Online (Sandbox Code Playgroud)

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

它是以另一个名称存在于标准C++库中,还是缺少?

And*_*son 143

C++ 98标准库中没有round().你可以自己写一个.以下是圆形上半部分的实现:

double round(double d)
{
  return floor(d + 0.5);
}
Run Code Online (Sandbox Code Playgroud)

C++ 98标准库中没有循环函数的可能原因是它实际上可以以不同的方式实现.以上是一种常见的方式,但还有其他一些方法,例如圆形到偶数,如果你要进行大量的四舍五入,那么偏差较小,通常会更好; 但实施起来有点复杂.

  • 这不能正确处理负数.litb的回答是正确的. (53认同)
  • 在截断之前添加0.5无法舍入到几个输入的最接近的整数,包括0.49999999999999994.见http://blog.frama-c.com/index.php?post/2013/05/02/nearbyintf1 (39认同)
  • @InnerJoin:是的,它处理负数与litb的答案不同,但这并不会使它"不正确". (38认同)
  • @MuhammadAnnaqeeb:你是对的,自从C++ 11发布以来,事情已经有了很大的改进.这个问题在另一个生活困难,欢乐很少的时候得到了回答.它仍然留在这里作为对当时生活和反抗的英雄以及那些仍然无法使用现代工具的可怜灵魂的颂歌. (15认同)
  • @ Sergi0:没有"正确"和"不正确",因为有[不止一个舍入的定义](http://en.wikipedia.org/wiki/Rounding#Tie-breaking)决定在中途发生的事情点.通过判断前,请检查您的事实. (10认同)
  • @SSpoke:它向上舍入到最接近的较大整数.-2> -3.它被称为"Round Half Up":http://en.wikipedia.org/wiki/Rounding#Round_half_up (6认同)
  • @Roddy不,它将负数舍入不正确的事实使它不正确. (5认同)
  • @Roddy考虑到Pascal Couq的三篇文章,在上面的评论中,关于这个操作有多么棘手,这个答案还不够.任何声称自己动手的答案都必须非常长.20多个downvotes也应该强烈表明这个答案有问题. (4认同)
  • 无需定义它.它符合C++ 11标准 (2认同)

Dan*_*olf 95

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
Run Code Online (Sandbox Code Playgroud)

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

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

  • 我已经在我的项目中使用了boost,为此+1,比使用天真的`floor(value + 0.5)`方法要好得多! (2认同)

Sha*_*our 81

C++ 03标准依赖于C90标准,标准称为标准C库,C++ 03标准草案(最接近 C++ 03的公开标准草案是N1804)部分1.2 规范性引用:

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

如果我们在cppreference上找到round,lround,llroundC文档,我们可以看到round和相关函数是C99的一部分,因此在C++ 03或之前不可用.

在C++ 11中,这种情况发生了变化,因为C++ 11依赖于C标准库的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 ;
}
Run Code Online (Sandbox Code Playgroud)

来自C99的另一个选项是std :: trunc,其中:

计算最大整数,其大小不大于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 ;

}
Run Code Online (Sandbox Code Playgroud)

如果你需要支持非C++ 11应用程序,你最好的选择是使用boost round,iround,lround,llroundboost trunc.

滚动你自己的版本很难

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

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

double myround(double d)
{
  return std::floor(d + 0.5);
}
Run Code Online (Sandbox Code Playgroud)

这将失败的一个输入是0.49999999999999994,(见它直播).

另一个常见的实现涉及将浮点类型转换为整数类型,在整数部分无法在目标类型中表示的情况下,可以调用未定义的行为.我们可以从C++标准部分的4.9 浮动积分转换中看到这一点,它说(强调我的):

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

例如:

float myround(float f)
{
  return static_cast<float>( static_cast<unsigned int>( f ) ) ;
}
Run Code Online (Sandbox Code Playgroud)

由于std::numeric_limits<unsigned int>::max()4294967295那么下面的电话:

myround( 4294967296.5f ) 
Run Code Online (Sandbox Code Playgroud)

会导致溢出,(见它直播).

通过查看在C中实现round()的简洁方法的答案,我们可以看出这是多么困难引用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;
}
Run Code Online (Sandbox Code Playgroud)

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

  • @downvoter请解释一下可以改进哪些方面?这里的绝大多数答案都是错误的,因为他们试图推出自己的回合,这种回合都以某种形式失败.如果我的解释中缺少某些内容,请告诉我. (5认同)
  • @chux有趣,IEEE 754-2008标准确实指出舍入保留了零和无穷大的符号(见5.9). (3认同)

kal*_*axy 71

值得注意的是,如果你想要一个舍入的整数结果,你不需要通过ceil或floor.也就是说,

int round_int( double r ) {
    return (r > 0.0) ? (r + 0.5) : (r - 0.5); 
}
Run Code Online (Sandbox Code Playgroud)

  • 不会给出0.49999999999999994的预期结果(当然,取决于你的期望,但0对我来说似乎比1更合理) (3认同)
  • 根据4.9 [conv.fpint],*“如果无法在目标类型中表示截断的值,则该行为未定义。” *,这有点危险。其他SO答案描述了如何稳健地执行此操作。 (3认同)
  • @stijn如果您不关心两个整数之间的值的四舍五入方向,那么所有值都可以。我不假思索地通过案例分析来证明这一点,有以下几种情况:0 &lt;= d &lt; 0.5、0.5 &lt;= d &lt; 1.5、1.5 &lt;= d &lt; 2^52、d &gt;= 2^52。我还详尽地测试了单精​​度情况。 (2认同)

小智 41

自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;
}
Run Code Online (Sandbox Code Playgroud)

输出:

round(0.5):  1
round(-0.5): -1
round(1.4):  1
round(-1.4): -1
round(1.6):  2
round(-1.6): -2
Run Code Online (Sandbox Code Playgroud)

  • 还有用于积分结果的`lround`和`llround` (2认同)

MSN*_*MSN 27

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

编辑:它可能不会被调用,因为我知道至少有三种舍入算法:舍入到零,舍入到最接近的整数,以及银行家的舍入.你要求舍入到最接近的整数.

  • 确实存在不同的舍入算法,它们都可以合理地声称是"正确的".但是,楼层(值+ 0.5)不是其中之一.对于某些值,例如0.49999997f或等效的double,答案是错误的 - 当所有人都同意它应该为零时,它将四舍五入为1.0.有关详细信息,请参阅此帖子:http://blog.frama-c.com/index.php?post/2013/05/02/nearbyintf1 (5认同)

小智 13

我们正在考虑两个问题:

  1. 四舍五入转换
  2. 类型转换.

舍入转换意味着舍入±float/double到最接近的floor/ceil float/double.可能是你的问题在这里结束.但是如果您希望返回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))
Run Code Online (Sandbox Code Playgroud)

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


Phi*_*ipp 11

在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;
}
Run Code Online (Sandbox Code Playgroud)

请注意,这仅适用于进行整数转换.

  • Boost还提供一组简单的舍入功能; 看到我的回答. (2认同)

Pet*_*des 7

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

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

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

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

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


x86-64 和 AArch64 的 gcc/clang

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

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

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

  • 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:默认内联到单个指令

四舍五入到int/ long/ long long

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

long l = lrint(x);

int  i = (int)rint(x);
Run Code Online (Sandbox Code Playgroud)

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

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

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

  • x86 clang with/without -ffast-math -msse4.1: 内(int/long)rint联到roundsd/ cvttsd2si。(错过了优化cvtsd2si)。 lrint根本不内联。

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

  • 不带-ffast-math: 的x86 gcc7 分别进行舍入(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非常慢。

  • +1 用于推荐 `rint()`,这是一个可行的选择,通常是这种情况。我猜“round()”这个名字暗示了一些程序员这就是他们想要的,而“rint()”似乎很神秘。请注意,`round()` 不使用“时髦”的舍入模式:round-to-nearest-ties-away 是官方的 IEEE-754 (2008) 舍入模式。奇怪的是 `nearbyint()` 没有被内联,因为它与 `rint()` 基本相同,并且在 `-ffast-math` 条件下应该是 *相同的*。这对我来说看起来很糟糕。 (2认同)

Car*_*arl 6

您可以使用以下方法舍入到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;
}
Run Code Online (Sandbox Code Playgroud)

  • 除非你的编译器int size默认为1024位,否则这对于巨大的双倍来说是不准确的... (4认同)
  • 当然,但问题是通用轮,你无法控制如何使用它.在没有ceil和floor的情况下,没有理由进行回合失败. (3认同)

dsh*_*hin 5

如果您最终想要将函数的double输出转换round()为a int,那么此问题的可接受解决方案将类似于:

int roundint(double r) {
  return (int)((r > 0.0) ? floor(r + 0.5) : ceil(r - 0.5));
}
Run Code Online (Sandbox Code Playgroud)

当以均匀随机值传递时,我的机器上的时钟大约为8.88 ns.

据我所知,以下在功能上是等效的,但在我的机器上的时钟频率为2.48 ns,具有显着的性能优势:

int roundint (double r) {
  int tmp = static_cast<int> (r);
  tmp += (r-tmp>=.5) - (r-tmp<=-.5);
  return tmp;
}
Run Code Online (Sandbox Code Playgroud)

性能更好的原因之一是跳过分支.