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

时间:2015-09-23 18:05:06

标签: c++ c floating-point ieee-754 bankers-rounding

C标准库提供C99中的roundlroundllround系列函数。但是,这些功能不符合IEEE-754标准,因为它们没有实现IEEE规定的半个到偶数的“银行家舍入”。如果小数分量恰好为0.5,则半到均匀舍入要求将结果四舍五入到最接近的偶数值。相反,如cppreference.com

所述,C99标准要求半距离零
  

1-3)计算最接近arg的整数值(浮点格式),无论当前的舍入模式如何,都将中间情况舍入为零,无论当前的舍入模式如何。

在C中实现舍入的通常的临时方法是表达式(int)(x + 0.5f),尽管在严格的IEEE-754数学中是incorrect,但通常由编译器将其转换为正确的cvtss2si指令。但是,这肯定不是一个可移植的假设。

如何实现一个用半到偶语义舍入任何浮点值的函数?如果可能,该函数应仅依赖于语言和标准库语义,以便它可以在非IEEE浮点类型上运行。如果这不可能,则根据IEEE-754位表示定义的答案也是可接受的。请根据<limits.h><limits>来描述任何常量。

6 个答案:

答案 0 :(得分:5)

舍入数字x,如果x和round(x)之​​间的差值正好是+0.5或-0.5,而round(x)是奇数,那么round(x)在错误的方向上舍入,所以你减去与x的区别。

答案 1 :(得分:5)

  

C标准库提供C99中的roundlroundllround系列函数。但是,这些功能不符合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 ++中,最后五个操作绑定到truncceilfloorroundrint函数,分别。 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绑定,您就可以使用roundevenfromfp函数来执行此操作,而无需控制舍入模式。

答案 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

     
      
  1. 数= 2.5
  2.   
  3. temp = floor(2.5)%2 = 2%2 = 0
  4.   
  5. x = -1 + temp = -1
  6.   
  7. x *错误+数字= 2.40009
  8.   
  9. round(2.40009)= 2
  10.         

    注意:此处的错误为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将解决问题。

C++ Reference for std::nearbyint