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

double round(double);

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

我可以在math.h中找到ceil()和floor() -但没有round()。

它是否以另一个名字出现在标准c++库中,或者它是否丢失了??


它通常实现为下限(值+ 0.5)。

编辑:它可能不叫四舍五入,因为我知道至少有三种四舍五入算法:四舍五入到零,四舍五入到最接近的整数,以及银行家的四舍五入。你要求的是最接近的整数。


编者注:下面的答案提供了一个简单的解决方案,其中包含几个实现缺陷(参见Shafik Yaghmour的答案以获得完整的解释)。注意,c++ 11已经将std::round、std::lround和std::llround作为内置程序。

c++ 98标准库中没有round()。不过你可以自己写。下面是round-half-up的实现:

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

c++ 98标准库中没有循环函数的可能原因是它实际上可以以不同的方式实现。以上是一种常见的方法,但还有其他的方法,如四舍五入到偶数,如果你要做很多四舍五入,这种方法的偏差更小,通常更好;不过实现起来有点复杂。


我是这样做的:

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

你可以四舍五入到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;
}

这里有两个问题:

舍入转换 类型转换。

四舍五入转换意味着四舍五入±浮动/双到最近的地板/天花板浮动/双。 也许你的问题到此为止了。 但如果希望返回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


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

这可能是一种低效的肮脏的转换方式,但见鬼,它是有效的,哈哈。这很好,因为它适用于实际的浮点数。不仅仅是视觉上影响输出。


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

注意,这仅在执行到整数的转换时有效。


值得注意的是,如果想要从舍入中得到整数结果,则不需要通过上下限或上下限。也就是说,

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

函数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.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


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::round, std::lround和std::llround。


小心地板(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;
}

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


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

输出:

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

如果你最终想要将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;
}

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


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

c++ 03标准依赖于C90标准,该标准称为标准C库,该标准在c++ 03标准草案中涵盖(最接近c++ 03的公开标准草案是N1804) 1.2节规范参考:

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

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

在c++ 11中,这种情况发生了变化,因为c++ 11依赖于C标准库的C99草案标准,因此提供了std::round和for整型返回类型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, round, lround, llround或boost trunc。

滚出自己版本的圆很难

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

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

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

这将失败的一个输入是0.49999999999999994。

另一种常见的实现涉及将浮点类型转换为整型类型,在整型部分不能在目标类型中表示的情况下,可以调用未定义的行为。我们可以从c++标准草案第4.9节浮动积分转换中看到这一点,其中说(强调我的):

浮点类型的右值可以转换为类的右值 整数类型。转换截断;也就是小数部分 就会被丢弃。如果截断的值不能,则行为未定义 用目标类型表示。[…]

例如:

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可能是一个选择,因为它是一个经过良好测试的实现。


我在asm的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

如果您需要能够在支持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.4999999999999999994或类似值)时可能会出现问题。

或者,如果你有c++ 11兼容的编译器,你可以从它的<cmath>头文件中获取std::round(),并使用它来创建你自己的头文件来定义函数(如果它还没有定义的话)。但是请注意,这可能不是最佳解决方案,特别是如果您需要为多个平台编译时。


不需要实现任何东西,所以我不确定为什么这么多答案涉及定义、函数或方法。

C99中

我们有下面的and和header <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 Community既不会产生错误也不会产生警告。

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

有序数除法

当除两个序数时,其中T是短的,int,长,或另一个序数,舍入表达式是这样的。

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

精度

毫无疑问,浮点运算中会出现奇怪的错误,但这只是在数字出现时才会出现,与四舍五入无关。

来源不仅仅是IEEE浮点数表示的尾数中的有效数字的数量,它与我们作为人类的十进制思维有关。

10是5和2的乘积,5和2是相对质数。因此,IEEE浮点标准不可能完美地表示为所有二进制数字表示的十进制数。

这不是舍入算法的问题。在选择类型和设计计算、数据输入和数字显示时,应该考虑到数学现实。如果应用程序显示的数字显示了这些十进制-二进制转换问题,那么该应用程序在视觉上表达了数字现实中不存在的、应该更改的准确性。


正如在评论和其他回答中指出的那样,ISO c++标准库直到ISO c++ 11才添加round(),当时该函数是通过引用ISO C99标准数学库而引入的。

For positive operands in [½, ub] round(x) == floor (x + 0.5), where ub is 223 for float when mapped to IEEE-754 (2008) binary32, and 252 for double when it is mapped to IEEE-754 (2008) binary64. The numbers 23 and 52 correspond to the number of stored mantissa bits in these two floating-point formats. For positive operands in [+0, ½) round(x) == 0, and for positive operands in (ub, +∞] round(x) == x. As the function is symmetric about the x-axis, negative arguments x can be handled according to round(-x) == -round(x).

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

我使用Intel编译器版本13对my_roundf()与newlib roundf()实现进行了详尽的测试,同时使用/fp:strict和/fp:fast。我还检查了newlib版本是否与Intel编译器mathimf库中的roundf()匹配。对于双精度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;
}

现在,使用包含C99/ c++ 11数学库的c++ 11编译器应该不是问题。但接下来的问题是:选择哪个舍入函数?

C99/ c++ 11 round()通常不是你想要的舍入函数。它使用了一种时髦的舍入模式,在一半的情况下(+-xxx.5000)舍入0作为抢七。如果你确实特别想要这种舍入模式,或者你的目标是一个round()比rint()更快的c++实现,那么就使用它(或者用这个问题的其他答案之一来模仿它的行为,从表面上看,仔细地复制特定的舍入行为)。

round()的舍入不同于IEEE754默认的舍入到最接近的模式,以偶数作为抢七。最接近偶数避免了数字平均大小的统计偏差,但确实偏向偶数。

有两个数学库舍入函数使用当前默认的舍入模式:std::nearbyint()和std::rint(),它们都是在C99/ c++ 11中添加的,所以它们在std::round()存在的任何时候都可用。唯一的区别是nearbyint从不引发FE_INEXACT。

出于性能考虑,更倾向于rint(): gcc和clang都更容易内联它,但gcc从不内联nearbyint()(即使使用- fast-math)


gcc/clang用于x86-64和AArch64

我把一些测试函数放在Matt Godbolt的编译器资源管理器上,在那里你可以看到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).

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

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 rint when 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


舍入到int / long / long:

你有两个选择:使用lrint(像rint一样,但返回long,或llrint返回long long),或使用FP->FP四舍五入函数,然后以正常的方式(带截断)转换为整数类型。有些编译器的一种优化方式比另一种更好。

long l = lrint(x);

int  i = (int)rint(x);

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

On x86, an FP->integer conversion that overflows the integer produces INT_MIN or LLONG_MIN (a bit-pattern of 0x8000000 or the 64-bit equivalent, with just the sign-bit set). Intel calls this the "integer indefinite" value. (See the cvttsd2si manual 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.

还要注意,从unsigned int / long到/从unsigned int / long的FP在x86上效率较低(没有AVX512)。在64位机器上转换为32位无符号是非常便宜的;只需转换为64位符号并截断即可。但除此之外,它明显变慢了。

x86 clang with/without -ffast-math -msse4.1: (int/long)rint inlines to roundsd / cvttsd2si. (missed optimization to cvtsd2si). lrint doesn't inline at all. x86 gcc6.x and earlier without -ffast-math: neither way inlines x86 gcc7 without -ffast-math: (int/long)rint rounds and converts separately (with 2 total instructions of SSE4.1 is enabled, otherwise with a bunch of code inlined for rint without roundsd). lrint doesn'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)rint inlines to 2 instructions. lrint doesn't inline AArch64 gcc6.3 with -ffast-math: (int/long)rint compiles to a call to lrint. lrint doesn't inline. This may be a missed optimization unless the two instructions we get without -ffast-math are very slow.


将浮点值四舍五入小数“n”位的最佳方法如下,在O(1)时间内:-

我们必须将值四舍五入3位,即n=3。所以,

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

从c++ 11开始简单地:

#include <cmath>
std::round(1.1)

或者得到int

static_cast<int>(std::round(1.1))

round_f for ARM with math

static inline float round_f(float value)
{
    float rep;
    asm volatile ("vrinta.f32 %0,%1" : "=t"(rep) : "t"(value));
    return rep;
}

没有数学的ARM的round_f

union f__raw {
    struct {
        uint32_t massa  :23;
        uint32_t order  :8;
        uint32_t sign   :1;
    };
    int32_t     i_raw;
    float       f_raw;
};

float round_f(float value)
{
    union f__raw raw;
    int32_t exx;
    uint32_t ex_mask;
    raw.f_raw = value;
    exx = raw.order - 126;
    if (exx < 0) {
        raw.i_raw &= 0x80000000;
    } else if (exx < 24) {
        ex_mask = 0x00ffffff >> exx;
        raw.i_raw += 0x00800000 >> exx;
        if (exx == 0) ex_mask >>= 1;
        raw.i_raw &= ~ex_mask;
    };
    return  raw.f_raw;
};