round()用于C ++中的float


232

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

double round(double);

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

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

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


1
例如,如果您只想将数字输出为四舍五入的数字,您似乎可以这样做std::cout << std::fixed << std::setprecision(0) << -0.9
弗兰克

43
保护这一点...具有出色新舍入方案的新用户应首先阅读现有答案。
Shog9 2011年

12
round自C ++ 11在中可用<cmath>。不幸的是,如果你是在Microsoft Visual Studio它仍然缺少:connect.microsoft.com/VisualStudio/feedback/details/775474/...
亚历山德罗Jacopson

3
正如我在回答中所指出的那样,滚动自己round有很多警告。在C ++ 11之前,该标准依赖于C90,其中不包括round。C ++ 11依赖于C99,它确实具有,round但正如我所指出的,C99 trunc具有不同的属性,根据应用程序可能更合适。大多数答案似乎也忽略了用户可能希望返回具有更多问题的整数类型。
Shafik Yaghmour 2014年

2
@uvts_cvs这似乎不是Visual Studio最新版本的问题,请实时查看
Shafik Yaghmour 2014年

Answers:


144

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

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

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


53
这不能正确处理负数。litb的答案是正确的。
注册用户

39
@InnerJoin:是的,它对负数的处理方式与litb的答案不同,但这并不会使它“不正确”。
罗迪

39
对于包括0.49999999999999994在内的多个输入,截断前加0.5不能舍入到最接近的整数。参见blog.frama-c.com/index.php?post/2013/05/02/nearbyintf1
Pascal Cuoq

10
@ Sergi0:没有“正确”和“不正确”,因为舍入的定义不止一个,它决定了在中点发生什么。在通过判断之前,请检查您的事实。
2013年

16
@MuhammadAnnaqeeb:是的,自C ++ 11发行以来,情况有了很大的改善。在生活艰难而欢乐却很少的另一个时代,这个问题又被问及回答了。这里仍然是对那些曾经生活和战斗过的英雄以及那些仍然无法使用现代工具的可怜人的颂歌。
Andreas Magnusson 2014年

96

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

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

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


2
我已经在自己的项目中使用了boost,为此+1,比使用朴素的floor(value + 0.5)方法要好得多!
Gustavo Maciel 2014年

@GustavoMaciel我知道我对游戏有些迟了,但是提升实现是floor(value + 0.5)
n。代词

它实际上并没有:github.com/boostorg/math/blob/develop/include/boost/math/…4年后 ,我也想说这floor(value + 0.5)并不是天真,而是取决于上下文和性质要舍入的值!
Gustavo Maciel '18

84

C ++ 03标准依赖于C90标准,该标准称为标准C库,C ++ 03标准草案中涵盖了该标准C库(与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 ;
}

同样来自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 ;

}

如果您需要支持非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);
}

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

另一种常见的实现方式涉及将浮点类型转换为整数类型,如果整数部分无法用目标类型表示,则可以调用未定义的行为。我们可以从C ++标准草案的4.9 浮点整数转换部分看到这一点,其中说:强调我的):

浮点类型的prvalue可以转换为整数类型的prvalue。转换被截断;即,小数部分被丢弃。如果无法在目标类型中表示截断的值,则该行为是不确定的。[...]

例如:

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

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

myround( 4294967296.5f ) 

会导致溢出,(请参阅实况)。

通过查看在C中实现round()的简洁方法的答案,我们可以看出这到底有多困难引用新版本的单精度浮点舍入。对于看起来很简单的东西,这是一个非常长的功能。不太了解浮点实现的人似乎不可能正确实现此功能:

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

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


5
@downvoter请解释可以改善的地方?这里的绝大多数答案都是错误的,因为他们试图进行自己的回合,但都失败了。如果我的解释中有遗漏,请告诉我。
沙菲克·雅格慕

1
不错的完整答案-尤其是低于0.5的部分。另一个利基市场:round(-0.0)。C规范似乎未指定。我期待-0.0结果。
chux-恢复莫妮卡2015年

3
@chux很有趣,IEEE 754-2008标准确实指定了舍入保留零和无穷大的符号(请参阅5.9)。
罗斯兰

1
@Shafik,这是一个很好的答案。我从来没有想过即使舍入也不是简单的操作。
罗斯兰

1
也许值得一提的是,由于数字和性能方面的原因,std::rint()它通常比std::round()使用C ++ 11时更可取。与round()的特殊模式不同,它使用当前的舍入模式。在x86上,它rint可以内联到一条指令,效率更高。(即使没有-ffast-math godbolt.org/g/5UsL2e,gcc和clang都可以做到,而只有clang内联了几乎等效的代码nearbyint())ARM支持单指令round(),但是在x86上,它只能内联多条指令,并且只能与-ffast-math
Peter Cordes

71

可能值得注意的是,如果您希望取整得到整数结果,则不需要将其传递给ceil或floor。即

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

3
虽然没有给出0.49999999999999994的预期结果(嗯,当然,这取决于您的预期,但是0对我而言似乎比1更合理)
stijn

@stijn好抓住。我发现在我的常量中添加长双字面量后缀可以解决您的示例问题,但是我不知道是否还有其他精度示例无法捕获。
kalaxy

1
顺便说一句,如果您添加0.49999999999999994而不是0.5,则对于0.49999999999999994和5000000000000001.0作为输入都可以正常工作。但是不确定所有值是否都可以,而且我找不到任何引用说明这是最终解决办法。
stijn

1
@stijn对于所有值都可以,如果您不在乎将两个整数之间的值四舍五入的方向。不假思索,我将通过以下案例的案例分析来证明这一点:0 <= d <0.5,0.5 <= d <1.5,1.5 <= d <2 ^ 52,d> = 2 ^ 52。我还详尽测试了单精​​度情况。
Pascal Cuoq

3
根据4.9 [conf.fpint],“如果无法在目标类型中表示截断的值,则该行为未定义。” ,所以这有点危险。其他SO答案描述了如何稳健地执行此操作。
托尼·德罗伊

41

从C ++ 11开始在cmath中可用(根据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;
}

输出:

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
也有lroundllround对整体结果
sp2danny

@ sp2danny:或者更好,lrint使用当前的舍入模式,而不是round时髦的远离零的抢七。
彼得·科德斯

27

通常实现为floor(value + 0.5)

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


1
区分不同版本的“回合”是很好的。知道何时选择哪个也很高兴。
xtofl

5
确实存在不同的舍入算法,所有这些算法都可以合理地断言“正确”。但是,下限(值+ 0.5)不是其中之一。对于某些值,例如0.49999997f或等效的double,答案是错误的-当所有人都同意应为零时,它将四舍五入为1.0。详情请参阅该帖子:blog.frama-c.com/index.php?
Bruce Dawson

14

我们正在研究两个问题:

  1. 舍入转换
  2. 类型转换。

四舍五入转换意味着四舍五入±浮点数/倍数到最近的底面/天花板浮点数/倍数。可能是您的问题到此为止。但是,如果希望返回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))

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


使用LONG_MIN-0.5LONG_MAX+0.5 引入并发症,因为数学可能并不精确。 LONG_MAX可能会超出double精确转换的精度。进一步可能的需求assert(x < LONG_MAX+0.5); (<vs <=)LONG_MAX+0.5可能是可以精确表示的,并且(x)+0.5可能具有导致转换LONG_MAX+1失败的确切结果long。其他角落问题也是如此。
chux-恢复莫妮卡

不要调用您的函数round(double),该名称已经有一个标准的数学库函数(在C ++ 11中),因此令人困惑。使用std::lrint(x)如果它是可用的。
彼得·科德斯

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

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


2
Boost还提供了一组简单的舍入函数;看我的答案。
丹尼尔·沃尔夫

boost:numeric::RoundEven< double >::nearbyint如果不想整数,也可以直接使用。@DanielWolf注意,简单函数使用+0.5实现,这存在aka.nice提出的问题
stijn 2013年

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

4
除非您的编译器int大小默认为1024位,否则对于大的double值将不准确...
aka.nice2012年

我认为使用它时可以接受:如果您的double值是1.0 e + 19,则舍入到3位是没有意义的。
卡尔

3
可以,但是问题是针对通用回合,您无法控制将如何使用它。没有理由让回合失败,而天花板和地板不会失败。
aka.nice 2012年

超出范围的args具有不确定的行为int。(实际上,在x86上,超出范围的FP值会将CVTTSD2SI产生的0x80000000值作为整数位模式生成,即INT_MIN,然后将其转换回为double
Peter Cordes

5

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

round()实际上, C99 / C ++ 11 并不是您想要的舍入函数。它采用了一种时髦的舍入模式,在中途决胜局(+-xxx.5000)时,它会从0舍入为平局。如果您确实确实想要这种舍入模式,或者您要针对round()比更快的C ++实现rint(),则可以使用它(或使用此问题的其他答案之一来模拟其行为,从而使它具有表面价值并仔细地重现该特定含义。四舍五入的行为。)

round()的四舍五入与IEEE754默认四舍五入(即使是抢七式)也不同。近偶数避免了平均数幅度的统计偏差,但确实偏向偶数。

有两个数学库舍入函数使用当前的默认舍入模式:std::nearbyint()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上,在Windows上标准调用约定没有调用保留的寄存器,因此编译器无法在整个Windows的XMM寄存器中保留任何FP值。call。因此,即使您不太了解asm,也仍然可以轻松地看到它仅仅是对库函数的尾部调用,还是内联到一两个数学指令。内联到一条或两条指令的任何内容都比函数调用(对于x86或ARM上的此特定任务)要好。

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

  • std::nearbyint()

    • x86 clang:内联到单个insn -msse4.1
    • x86 gcc:仅使用-msse4.1 -ffast-mathgcc 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默认值和大多数其他代码都支持HW。)
  • 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);

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

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

还要注意,unsigned在x86上(不使用AVX512),FP往返int / long的效率较低。在64位计算机上转换为32位unsigned非常便宜;只需转换为64位带符号并截断即可。但是,否则它的运行速度会大大降低。

  • 带/不带x86的clang -ffast-math -msse4.1:/的内(int/long)rint联。(缺少对的优化)。 根本不内联。roundsdcvttsd2sicvtsd2silrint

  • x86 gcc6.x和更早版本,不含-ffast-math:均无内联

  • 不带x的x86 gcc7 -ffast-math:分别(int/long)rint四舍五入和转换(启用了2条SSE4.1指令,否则,使用内联的代码串rint不带roundsd)。 lrint不内联。
  • x86 gcc 具有 -ffast-math内联至cvtsd2si(最佳)所有方式,无需SSE4.1。

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

  • AArch64 gcc6.3带有-ffast-math(int/long)rint编译为对的调用lrintlrint不内联。除非我们没有给出的两条指令-ffast-math非常慢,否则这可能是错过的优化。

TODO:ICC和MSVC也可以在Godbolt上使用,但是我没有查看它们的输出。欢迎编辑...另外:先按编译器/版本进行分解,然后按其中的函数进行分解是否会更有用?大多数人不会根据编译FP-> FP或FP->整数舍入的程度来切换编译器。
彼得·科德斯

2
推荐+1 rint()是可行的选择,通常是这种情况。我想这个名称round()对某些程序员意味着这就是他们想要的,尽管rint()看起来很神秘。请注意,round()它不使用“笨拙的”舍入模式:舍入到最近的舍入是官方的IEEE-754(2008)舍入模式。奇怪的是nearbyint()没有内联,因为它与基本上相同rint(),并且在条件下应该相同-ffast-math。对我来说,这似乎有点bug。
njuffa

4

当心floor(x+0.5)。这是[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

这是http://bugs.squeak.org/view.php?id=7134。使用类似@konik的解决方案。

我自己的健壮版本将是这样的:

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

这里给出避免floor(x + 0.5)的另一个原因。


2
我有兴趣了解下降投票。是因为平局解决了从零开始而不是到最接近的偶数?
aka.nice 2014年

1
注意:C规范指出“无论当前舍入方向如何,都将舍入到零的中间位置。”因此,不考虑奇/偶的舍入是合规的。
chux-恢复莫妮卡2015年

4

如果您最终希望将函数的double输出转换为,则该问题的可接受解决方案将如下所示:round()int

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

当传入一致的随机值时,这在我的机器上大约为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;
}

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


超出范围的args具有不确定的行为int。(在在x86实践中,外的范围FP值会使CVTTSD2SI农产品0x80000000作为所述整数位模式,即INT_MIN,其然后将被转换回double
彼得科尔德

2

无需执行任何操作,因此我不确定为什么这么多答案涉及定义,函数或方法。

在C99中

对于类型泛型宏,我们有以下和标题<tgmath.h>。

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

如果您不能编译它,则可能已遗漏了数学库。与此类似的命令可在我拥有的每个C编译器上运行(多个)。

gcc -lm -std=c99 ...

在C ++ 11中

#include <cmath>中有以下附加过载,它们依赖于IEEE双精度浮点。

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

在std名称空间中也有等效项

如果您不能编译它,则可能是使用C编译而不是C ++。对于g ++ 6.3.1,x86_64-w64-mingw32-g ++ 6.3.0,clang-x86_64 ++ 3.8.0和Visual C ++ 2015社区,以下基本命令既不会产生错误也不会发出警告。

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

有序部门

当除以T为short,int,long或另一个序数的两个序数时,舍入表达式为this。

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

准确性

毫无疑问,浮点运算中会出现奇怪的外观错误,但这仅在数字出现时才出现,并且与舍入无关。

来源不仅仅是浮点数的IEEE表示形式的尾数中的有效位数,它还与我们人类的十进制思维有关。

十是五和二的乘积,而五和二是相对质数。因此,对于所有二进制数字表示形式,IEEE浮点标准可能无法完美地表示为十进制数。

舍入算法不是问题。在选择类型和设计计算,输入数据以及显示数字时应考虑到数学上的现实。如果应用程序显示显示这些十进制二进制转换问题的数字,则该应用程序在视觉上表示数字现实中不存在的精度,应进行更改。


1
“我不确定为什么这么多答案涉及定义,函数或方法。” 看看何时被问到-C ++ 11尚未发布。;)
jaggedSpire

@jaggedSpire,如果您认为合适的话,请给我个竖起大拇指,因为在当今最常用的编译器中,所有高分答案都已过时且具有误导性。
FauChristian

2

功能double round(double)配合使用modf功能:

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

为了编译干净,必须包含“ math.h”和“ limits”。该函数根据以下舍入模式工作:

  • 5.0的一轮是5.0
  • 3.8的回合是4.0
  • 2.3的回合是2.0
  • 1.5的整数是2.0
  • 0.501的整数是1.0
  • 0.5的整数是1.0
  • 0.499的取整为0.0
  • 舍入0.01为0.0
  • 0.0的一轮是0.0
  • -0.01的整数是-0.0
  • -0.499的一轮是-0.0
  • -0.5的一轮是-0.0
  • -0.501的一轮是-1.0
  • -1.5的一轮是-1.0
  • -2.3的回合是-2.0
  • -3.8的一轮是-4.0
  • -5.0的一轮是-5.0

2
这是一个很好的解决方案。我不确定将-1.5舍入为-1.0是标准的,但我希望通过symetry将-2.0舍入。另外,我也看不到后卫的位置,如果可以删除前两个的话。
aka.nice 2012年

2
我检查了ISO / IEC 10967-2标准open-std.org/jtc1/sc22/wg11/docs/n462.pdf,并从附录B.5.2.4开始,舍入函数必须确实是对称的,rounding_F(x)= neg_F(rounding_F(neg_F(x)))
aka.nice 2012年

与C ++ 11 rint()或相比,这会比较慢nearbyint(),但是如果您真的不能使用提供适当舍入功能的编译器,并且需要的精度超过性能……
Peter Cordes

1

如果需要在支持C ++ 11标准的环境中编译代码,但又需要在不支持C ++ 11标准的环境中编译相同的代码,则可以使用函数宏在std之间进行选择。 :: round()和每个系统的自定义函数。只需传递-DCPP11或传递/DCPP11给符合C ++ 11的编译器(或使用其内置的版本宏),然后制作一个标头,如下所示:

// 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 */

有关快速示例,请访问http://ideone.com/zal709

在不符合C ++ 11的环境中,这近似于std :: round(),包括保留-0.0的符号位。但是,这可能会导致性能下降,并且可能会舍入某些已知的“问题”浮点值,例如0.49999999999999994或类似的值。

另外,如果您可以访问C ++ 11兼容的编译器,则可以从其<cmath>标头中获取std :: round(),并使用它来创建自己的标头,以定义该函数(如果尚未定义)。请注意,这可能不是最佳解决方案,尤其是当您需要针对多个平台进行编译时。


1

根据Kalaxy的响应,以下是一个模板化解决方案,该解决方案基于自然舍入将任何浮点数舍入为最接近的整数类型。如果该值超出整数类型的范围,也会在调试模式下引发错误,从而大致用作可行的库函数。

    // 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
正如我在答案中指出的那样,添加0.5并非在所有情况下都有效。尽管至少您可以处理溢出问题,但是可以避免未定义的行为。
Shafik Yaghmour 2014年

1

正如评论和其他答案中指出的那样,ISO C ++标准库未添加 round()直到通过引用ISO C99标准数学库引入了该函数之后到ISO C ++ 11中。

在正操作数[½,UB ] round(x) == floor (x + 0.5),其中UB是2 23用于float当映射到IEEE-754(2008) binary32,和2 52用于double当它被映射到IEEE-754(2008) binary64。数字23和52对应于这两种浮点格式中存储的尾数位的数量。对于[+0,½)round(x) == 0中的正操作数,以及(ub,+∞]中的正操作数,round(x) == x由于该函数关于x轴对称,x因此可以根据来处理负参数round(-x) == -round(x)

这导致下面的紧凑代码。它可以在各种平台上编译成合理数量的机器指令。我观察了GPU上最紧凑的代码,其中my_roundf()需要大约十二条指令。根据处理器体系结构和工具链的不同,这种基于浮点的方法可能比在不同答案中引用的newlib中基于整数的实现更快或更慢。

我测试了my_roundf()详尽地对newlib roundf()使用英特尔编译器版本13的实现,既/fp:strict/fp:fast。我还检查了newlib版本相匹配的roundf()mathimf英特尔编译器的库。不可能对双精度进行详尽的测试round(),但是代码在结构上与单精度实现相同。

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

我进行了编辑以避免假定int宽度超过16位。当然,它仍然假定float是4字节IEEE754 binary32。C ++ 11 static_assert或宏#ifdef/ #error可以检查。(但是,当然,如果C ++ 11可用,则应使用std::round,或者对于当前的舍入模式,应使用std::rintgcc和clang很好地内联)。
彼得·科德斯

顺便说一句,先内gcc -ffast-math -msse4.1std::round()add( AND(x, L1), OR(x,L2),然后再内联roundsd。即就round而言,它相当有效地实现了rint。但是没有理由在C ++源代码中手动执行此操作,因为如果您拥有std::rint()std::nearbyint()也拥有std::round()。请参阅我的答案,以获取无聊的链接以及使用不同gcc / clang版本的内联与否的摘要。
彼得·科德斯

@PeterCordes我很清楚如何round()有效地实现rint()(当后者以“从最接近点到最接近点或什至最接近点”模式运行时):我为CUDA标准数学库实现了这一点。但是,这个问题似乎在询问如何round()在C ++ 11之前使用C ++ 进行实现,因此rint()也只能使用floor()ceil()
njuffa

@PeterCordes对不起,我误会了。round()从容易合成rint()舍入到零模式,又名trunc()。在喝第一杯咖啡之前不应该做出回应。
njuffa

1
@PeterCordes我同意OP可能不需要的特定舍入行为round()。最根本的程序员不知道的区别之间round()VS rint()与舍入到最接近的偶数,其中后者通常是由硬件,并因此更有效地直接设置; 我在《 CUDA编程指南》中清楚地说明了这一点,以使程序员知道:“建议将单精度浮点操作数舍入为整数,结果是单精度浮点数为rintf(),而不是roundf()”。
njuffa

0

我在x86架构和特定于MS VS的C ++中使用以下在round中的回合实现:

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

UPD:返回双值

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

输出:

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

结果值应为具有双精度的浮点值。
真相搜索者,2015年

// @ trueseeker:是的,我必须查看所需的返回值类型。好,请参阅“ UPD”。
Aleksey F. 2015年

希望编译器将内联rint()nearbyint()插入SSE4.1 roundsd指令或x87 frndint指令,这将比在寄存器中的数据上使用此内联asm所需的两次存储/重装往返过程快得多。MSVC内联汇编非常麻烦,frndint因为它们无法包装单个指令,因为无法将输入输入寄存器。在st(0)返回结果的函数末尾使用它可能是可靠的返回输出的方式。显然,这eax对于整数是安全的,即使它内联包含asm的函数也是如此。
彼得·科德斯

@PeterCordes欢迎现代优化。但是,我当时无法使用SSE4.1,因为它当时不存在。我的目的是提供最小的回合实现方式,即使在2000年代的旧Intel P3或P4家族中也可以使用。
Aleksey F.

P3甚至没有SSE2,所以编译器将已经使用的x87的double,因此应该能够发出frndint自己的rint()。如果您的编译器使用的是SSE2,则将doubleXMM寄存器中的a反弹到x87并返回可能不值得。
彼得·科德斯

0

舍入一个小数点后的“ n”个小数的最佳方法是在O(1)时间中如下:

我们必须将值四舍五入为n = 3。因此,

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

这可能是一种效率低下的肮脏转换方式,但是它确实有效。很好,因为它适用于实际的浮动。不只是视觉上影响输出。


这是欢快低效的,并且还它截断(由总是丢弃尾随位),而不是舍入到最接近的。
彼得·科德斯

-6

我这样做:

#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
您不是说pow(10,place)而不是10 ^ place中的二元运算符^吗?我机器上的10 ^ 2给我8 !! 不过,在我的Mac 10.7.4和gcc上,代码不起作用,返回了原始值。
Pete855217
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.