C标准库提供C99中的round
,lround
和llround
系列函数。但是,这些功能不符合IEEE-754标准,因为它们没有实现IEEE规定的半个到偶数的“银行家舍入”。如果小数分量恰好为0.5,则半到均匀舍入要求将结果四舍五入到最接近的偶数值。相反,如cppreference.com
1-3)计算最接近arg的整数值(浮点格式),无论当前的舍入模式如何,都将中间情况舍入为零,无论当前的舍入模式如何。
在C中实现舍入的通常的临时方法是表达式(int)(x + 0.5f)
,尽管在严格的IEEE-754数学中是incorrect,但通常由编译器将其转换为正确的cvtss2si
指令。但是,这肯定不是一个可移植的假设。
如何实现一个用半到偶语义舍入任何浮点值的函数?如果可能,该函数应仅依赖于语言和标准库语义,以便它可以在非IEEE浮点类型上运行。如果这不可能,则根据IEEE-754位表示定义的答案也是可接受的。请根据<limits.h>
或<limits>
来描述任何常量。
答案 0 :(得分:5)
舍入数字x,如果x和round(x)之间的差值正好是+0.5或-0.5,而round(x)是奇数,那么round(x)在错误的方向上舍入,所以你减去与x的区别。
答案 1 :(得分:5)
C标准库提供C99中的
round
,lround
和llround
系列函数。但是,这些功能不符合IEEE-754标准,因为它们没有实现&#34;银行家的舍入&#34;按照IEEE规定的一半甚至一半...
讨论单个功能是否符合IEEE-754&#34;是没有意义的。 IEEE-754合规性要求具有定义语义的一组数据类型操作可用。它不要求这些类型或操作具有特定名称,也不要求仅那些操作可用。实现可以提供它想要的任何附加功能并且仍然是兼容的。如果一个实现想要提供从舍入到舍入,舍入随机,从零开始的舍入,以及如果不精确的陷阱,它可以这样做。
IEEE-754舍入实际需要的是提供以下六个操作:
<强> convertToIntegerTiesToEven 强>(x)的
<强> convertToIntegerTowardZero 强>(x)的
<强> convertToIntegerTowardPositive 强>(x)的
<强> convertToIntegerTowardNegative 强>(x)的
<强> convertToIntegerTiesToAway 强>(x)的
<强> convertToIntegerExact 强>(x)的
在C和C ++中,最后五个操作绑定到trunc
,ceil
,floor
,round
和rint
函数,分别。 C11和C ++ 14没有第一个绑定,但未来的修订将使用roundeven
。如您所见,round
实际上是必需的操作之一。
但是,roundeven
在当前的实施中不可用,这将我们带到您问题的下一部分:
在C中实现舍入的常用ad-hoc方法是表达式
(int)(x + 0.5f)
,尽管在严格的IEEE-754数学中不正确,但通常由编译器将其转换为正确的cvtss2si
指令。但是,这肯定不是一个可移植的假设。
该表达式的问题远远超出了严格的IEEE-754数学&#34;。对于否定x
,它完全不正确,为nextDown(0.5)
提供了错误的答案,并将2 ** 23 binade中的所有奇数整数转换为偶数整数。任何将其转换为cvtss2si
的编译器都是可怕的,可怕的破坏。如果你有一个这样的例子,我很乐意看到它。
如何实现一个用半到偶语义舍入任何浮点值的函数?
在评论中注明 njuffa ,您可以确保设置默认的舍入模式并使用rint
(或lrint
,因为它听起来像您真正想要的整数结果),或者您可以通过调用round
来实现自己的舍入函数,然后修复像 gnasher729 建议的中途案例。一旦采用了针对C的n1778绑定,您就可以使用roundeven
或fromfp
函数来执行此操作,而无需控制舍入模式。
答案 2 :(得分:2)
使用C标准库中的remainder(double x, 1.0)
。这与当前的舍入模式无关。
余数函数计算IEC 60559
所需的余数x REM y
remainder()
在这里很有用,因为它符合OP与甚至要求的关系。
double round_to_nearest_ties_to_even(double x) {
x -= remainder(x, 1.0);
return x;
}
测试代码
void rtest(double x) {
double round_half_to_even = round_to_nearest_ties_to_even(x);
printf("x:%25.17le z:%25.17le \n", x, round_half_to_even);
}
void rtest3(double x) {
rtest(nextafter(x, -1.0/0.0));
rtest(x);
rtest(nextafter(x, +1.0/0.0));
}
int main(void) {
rtest3(-DBL_MAX);
rtest3(-2.0);
rtest3(-1.5);
rtest3(-1.0);
rtest3(-0.5);
rtest(nextafter(-0.0, -DBL_MAX));
rtest(-0.0);
rtest(0.0);
rtest(nextafter(0.0, +DBL_MAX));
rtest3(0.5);
rtest3(1.0);
rtest3(1.5);
rtest3(2.0);
rtest3(DBL_MAX);
rtest3(0.0/0.0);
return 0;
}
输出
x: -inf z: -inf
x:-1.79769313486231571e+308 z:-1.79769313486231571e+308
x:-1.79769313486231551e+308 z:-1.79769313486231551e+308
x: -2.00000000000000044e+00 z: -2.00000000000000000e+00
x: -2.00000000000000000e+00 z: -2.00000000000000000e+00
x: -1.99999999999999978e+00 z: -2.00000000000000000e+00
x: -1.50000000000000022e+00 z: -2.00000000000000000e+00
x: -1.50000000000000000e+00 z: -2.00000000000000000e+00 tie to even
x: -1.49999999999999978e+00 z: -1.00000000000000000e+00
x: -1.00000000000000022e+00 z: -1.00000000000000000e+00
x: -1.00000000000000000e+00 z: -1.00000000000000000e+00
x: -9.99999999999999889e-01 z: -1.00000000000000000e+00
x: -5.00000000000000111e-01 z: -1.00000000000000000e+00
x: -5.00000000000000000e-01 z: 0.00000000000000000e+00 tie to even
x: -4.99999999999999944e-01 z: 0.00000000000000000e+00
x:-4.94065645841246544e-324 z: 0.00000000000000000e+00
x: -0.00000000000000000e+00 z: 0.00000000000000000e+00
x: 0.00000000000000000e+00 z: 0.00000000000000000e+00
x: 4.94065645841246544e-324 z: 0.00000000000000000e+00
x: 4.99999999999999944e-01 z: 0.00000000000000000e+00
x: 5.00000000000000000e-01 z: 0.00000000000000000e+00 tie to even
x: 5.00000000000000111e-01 z: 1.00000000000000000e+00
x: 9.99999999999999889e-01 z: 1.00000000000000000e+00
x: 1.00000000000000000e+00 z: 1.00000000000000000e+00
x: 1.00000000000000022e+00 z: 1.00000000000000000e+00
x: 1.49999999999999978e+00 z: 1.00000000000000000e+00
x: 1.50000000000000000e+00 z: 2.00000000000000000e+00 tie to even
x: 1.50000000000000022e+00 z: 2.00000000000000000e+00
x: 1.99999999999999978e+00 z: 2.00000000000000000e+00
x: 2.00000000000000000e+00 z: 2.00000000000000000e+00
x: 2.00000000000000044e+00 z: 2.00000000000000000e+00
x: 1.79769313486231551e+308 z: 1.79769313486231551e+308
x: 1.79769313486231571e+308 z: 1.79769313486231571e+308
x: inf z: inf
x: nan z: nan
x: nan z: nan
x: nan z: nan
答案 3 :(得分:1)
float
数据类型可以表示8388608.0f到16777216.0f范围内的所有整数,但没有分数。任何大于8388607.5f的float
个数字都是整数,不需要舍入。将8388608.0f添加到任何小于它的非负float
将产生一个整数,该数将根据当前的舍入模式(通常为半舍入到偶数)进行舍入。减去8388608.0f将产生原始的正确圆形版本(假设它在合适的范围内)。
因此,应该可以做类似的事情:
float round(float f)
{
if (!(f > -8388608.0f && f < 8388608.0f)) // Return true for NaN
return f;
else if (f > 0)
return (float)(f+8388608.0f)-8388608.0f;
else
return (float)(f-8388608.0f)+8388608.0f;
}
并利用添加的自然舍入行为,而不必使用任何其他&#34; round-to-integer&#34;设施。
答案 4 :(得分:1)
以下是 round-half to even 程序的简单实现,该程序遵循IEEE四舍五入标准。
逻辑: 错误= 0.00001
- 数= 2.5
- temp = floor(2.5)%2 = 2%2 = 0
- x = -1 + temp = -1
- x *错误+数字= 2.40009
- round(2.40009)= 2
醇>注意:此处的错误为0.00001,即如果发生2.500001,那么它将舍入为2而不是3
Python 2.7实施:
temp = (number)
rounded_number = int( round(-1+ temp%2)*0.00001 + temp )
C ++实现:(使用 math.h 进行楼层功能)
float temp = (number)
int rounded_number = (int)( (-1+ temp%2)*0.00001 + temp + 0.5)
这将给出的输出如下:标准:
(3.5) - &gt; 4
(2.5) - &gt; 2
编辑1:正如@Mark Dickinson在评论中指出的那样。可以根据代码中的要求修改错误以使其标准化。对于python,要将其转换为可能的最小浮点值,您可以执行以下操作。
import sys
error = sys.float_info.min
答案 5 :(得分:0)
自C ++ 11起,STL中就有一个函数可以进行半数舍入。如果将浮点舍入模式设置为FE_TONEAREST
(默认设置),则std::nearbyint
将解决问题。