符合IEEE-754标准的半圆到偶数

68e*_*669 6 c c++ floating-point bankers-rounding ieee-754

C标准库提供的round,lroundllround家庭的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>.

chu*_*ica 6

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


gna*_*729 5

舍入数字x,如果x和round(x)之​​间的差值正好是+0.5或-0.5,而round(x)是奇数,则round(x)在错误的方向上舍入,因此您减去与X.

  • 是.`round(x) - x`的结果将是完全可表示的(假设是IEEE 754格式). (2认同)
  • 一个邪恶的伎俩是在`fabs(x - round(x))== 0.5`的情况下使用`2.0*round(0.5*x)`,这节省了一些有点乱的任务,弄清楚`round(x) `是偶数还是奇数. (2认同)

Ste*_*non 5

C标准库提供的round,lroundllround家庭的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绑定,您将能够使用roundevenfromfp函数来执行此操作,而无需控制舍入模式.