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

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

double round(double);


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

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

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

434262 次浏览

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

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

编者按:下面的答案提供了一个简单的解决方案,其中包含几个实现缺陷(完整的解释见Shafik Yaghmour的回答是)。注意,c++ 11已经内置了__ABC0, __ABC1,和std::llround

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

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

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

我是这样做的:

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

这里有两个问题:

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

from: 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

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

编辑:从c++ 11开始,有__ABC0, __ABC1,和std::llround. C。

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

避免下限(x+0.5)的另一个原因是在这里

自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

如果你最终想要将你的round()函数的double输出转换为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条和第7条中描述的库 ISO / IEC 9899 / Amd。1:1995以下称为标准C Library.1) < / p >

如果我们转到C文件为圆,圆,圆cppreference,我们可以看到和相关函数是C99的一部分,因此在c++ 03或更早的版本中不可用。

在c++ 11中,这种情况发生了变化,因为c++ 11依赖于C99草案标准的C标准库,因此提供了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应用程序,你最好的选择是使用转,转,转,转提高trunc

滚出自己版本的圆很难

自己卷可能不值得花力气,比看起来难:第1部分,将浮点数舍入为最接近的整数浮点数舍入为最接近的整数,第2部分浮点数舍入为最接近的整数,第3部分解释说:

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

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

这将失败的一个输入是0.49999999999999994, (see it live)。

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

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

.

例如:

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

假设std::numeric_limits<unsigned int>::max()4294967295,那么调用如下:

myround( 4294967296.5f )

将导致溢出,(see it live)。

通过查看在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;
}

另一方面,如果没有其他解决方案可用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中

我们有下面的和和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标准数学库引入的。

对于[½,乌兰巴托] round(x) == floor (x + 0.5)中的正操作数,其中乌兰巴托在映射到IEEE-754 (2008) binary32时为2float1,在映射到IEEE-754 (2008) binary64时为2float2。数字23和52对应于这两种浮点格式中的float3尾数位数。对于[+0,½)round(x) == 0中的正操作数,以及(乌兰巴托, +∞]round(x) == x中的正操作数。由于函数是关于x轴对称的,负参数x可以根据round(-x) == -round(x)来处理。

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

我使用英特尔编译器版本13使用/fp:strict/fp:fast对newlib roundf()实现详尽地测试了my_roundf()。我还检查了newlib版本是否与英特尔编译器的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()通常不是你想要的舍入函数。它使用一种时髦的舍入模式,在中途的情况下从0舍入作为抢七(+-xxx.5000)。如果你确实特别想要这种舍入模式,或者你的目标是一个c++实现,其中round()rint()快,那么就使用它(或者用这个问题的其他答案之一来模仿它的行为,从表面上看它并仔细地复制特定的舍入行为)。

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

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

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


gcc/clang用于x86-64和AArch64

我把一些测试函数放在Matt Godbolt的编译器资源管理器,在这里你可以看到source + asm输出(适用于多个编译器)。有关读取编译器输出的更多信息,请参见这Q&和Matt的CppCon2017演讲:

在FP代码中,内联小函数通常是一大优势。特别是在非windows上,标准调用约定没有调用保留寄存器,因此编译器不能通过call在XMM寄存器中保留任何FP值。因此,即使您并不真正了解asm,也可以很容易地看出它是否只是对库函数的尾部调用,或者它是否内联到一个或两个数学指令。任何内联到一个或两个指令的东西都比函数调用要好(对于x86或ARM上的这个特定任务来说)。

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

  • < p > std::nearbyint():

    • x86 clang:通过-msse4.1内联到单个insn。
    • x86 gcc:只能通过-msse4.1 -ffast-math内联到单个insn,并且只能在gcc 5.4和早些时候上内联。后来gcc从未内联它(也许他们没有意识到其中一个直接位可以抑制不精确异常?这就是clang使用的,但旧的gcc在内联rint时使用的是相同的immediate)
    • AArch64 gcc6.3:默认内联到单个insn。
    • 李< / ul > < / >
    • < p > std::rint:

      • x86 clang:通过-msse4.1内联到单个insn
      • x86 gcc7:用-msse4.1内联到单个insn。(没有SSE4.1,内联到几个指令)
      • x86 gcc6。x和更早的版本:内联到单个带有-ffast-math -msse4.1的insn。
      • AArch64 gcc:默认内联到单个insn
      • 李< / ul > < / >
      • < p > std::round:

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

          • x86 clang:通过-msse4.1内联到单个insn
          • x86 gcc7。x:内联到一个带有-msse4.1的单个insn
          • x86 gcc6。x和更早的版本:内联到单个带有-ffast-math -msse4.1的insn
          • AArch64 gcc:默认内联到一条指令
          • 李< / ul > < / >

          舍入到int / long / long long:

          这里有两个选项:使用lrint(类似rint但返回long,或long long返回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的情况下不会这样做。

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

          • x86 clang带/不带-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条指令,否则会内联一堆没有roundsdrint代码)。lrint不能内联。
          • x86 gcc with -ffast-math: all ways inline to cvtsd2si (optimal),不需要SSE4.1。

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

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

将浮点值四舍五入小数“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;
};