68e*_*669 6 c c++ floating-point bankers-rounding ieee-754
C标准库提供的round,lround和llround家庭的C99功能.但是,这些功能不符合IEEE-754标准,因为它们没有实现IEEE规定的半个到偶数的"银行家舍入".如果小数分量恰好为0.5,则半到均匀舍入要求将结果四舍五入到最接近的偶数值.相反,如cppreference.com上所述,C99标准要求半个零距离
1-3)计算最接近arg的整数值(浮点格式),将中间情况舍入为零,不管当前的舍入模式如何.
在C中实现舍入的通常的临时方法是(int)(x + 0.5f)尽管在严格的IEEE-754数学中不正确,但通常由编译器将其转换为正确的cvtss2si指令.然而,这肯定不是一个可移植的假设.
如何实现一个函数,它将使用半对偶语义对任何浮点值进行舍入?如果可能,该函数应仅依赖于语言和标准库语义,以便它可以在非IEEE浮点类型上运行.如果这不可能,则根据IEEE-754位表示定义的答案也是可接受的.请用<limits.h>或表示任何常数<limits>.
使用remainder(double x, 1.0)C标准库。这与当前的舍入模式无关。
余数函数计算IEC 60559 要求的余数 x REM y
remainder() 在这里很有用,因为它满足 OP 与偶数要求的联系。
double round_to_nearest_ties_to_even(double x) {
x -= remainder(x, 1.0);
return x;
}
Run Code Online (Sandbox Code Playgroud)
测试代码
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;
}
Run Code Online (Sandbox Code Playgroud)
输出
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
Run Code Online (Sandbox Code Playgroud)
舍入数字x,如果x和round(x)之间的差值正好是+0.5或-0.5,而round(x)是奇数,则round(x)在错误的方向上舍入,因此您减去与X.
C标准库提供的
round,lround和llround家庭的C99功能.但是,这些功能不符合IEEE-754标准,因为它们没有按照IEEE的要求实现半个到偶数的"银行家舍入"...
谈论个别功能是否符合"IEEE-754"是没有意义的.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中实现舍入的通常的临时方法是
(int)(x + 0.5f)尽管在严格的IEEE-754数学中不正确,但通常由编译器将其转换为正确的cvtss2si指令.然而,这肯定不是一个可移植的假设.
该表达式的问题远远超出了"严格的IEEE-754数学".这对于否定是完全错误的x,给出错误的答案nextDown(0.5),并将2**23 binade中的所有奇数整数转换为偶数整数.任何将其翻译成的编译器cvtss2si都是可怕的,可怕的破坏.如果你有一个这样的例子,我很乐意看到它.
如何实现一个函数,它将使用半对偶语义对任何浮点值进行舍入?
正如njuffa在评论中指出的那样,你可以确保设置并使用默认的舍入模式rint(或者lrint,听起来你实际上想要一个整数结果),或者你可以通过调用round然后修复中途案例来实现自己的舍入功能像gnasher729建议的那样.一旦采用了针对C的n1778绑定,您将能够使用roundeven或fromfp函数来执行此操作,而无需控制舍入模式.