首页 文章

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

提问于
浏览
5

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

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

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

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

5 回答

  • 5

    将数字x舍入,如果x和round(x)之间的差值恰好为0.5或-0.5,并且round(x)为奇数,则round(x)在错误的方向上舍入,因此您减去x的差值 .

  • 2

    C标准库在C99中提供round,lround和llround系列函数 . 但是,这些功能不符合IEEE-754标准,因为它们没有按照IEEE的要求实现半个到偶数的“银行家舍入”...

    谈论个别功能是否是"IEEE-754 compliant"是没有意义的 . IEEE-754合规性要求具有定义语义的一组数据类型操作可用 . 它不要求这些类型或操作具有特定名称,也不要求只有那些操作可用 . 实现可以提供它想要的任何附加功能并且仍然是兼容的 . 如果一个实现想要提供舍入到奇数,舍入随机,舍入为零和陷阱 - 如果不精确,它可以这样做 .

    IEEE-754实际需要进行四舍五入的是提供以下六种操作:

    convertToIntegerTiesToEven(x)convertToIntegerTowardZero(x)convertToIntegerTowardPositive(x)convertToIntegerTowardNegative(x)convertToIntegerTiesToAway(x)convertToIntegerExact(x)

    在C和C中,这些操作的最后五个分别绑定到 truncceilfloorroundrint 函数 . C11和C14没有第一个绑定,但未来的修订将使用 roundeven . 如您所见, round 实际上是必需的操作之一 .

    但是, roundeven 在当前的实现中不可用,这将我们带到您问题的下一部分:

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

    该表达式的问题远远超出"strict IEEE-754 math" . 负 x 完全不正确,为 nextDown(0.5) 给出了错误的答案,并将2 ** 23 binade中的所有奇数整数变为偶数整数 . 任何将它翻译成 cvtss2si 的编译器都是可怕的,可怕的破碎 . 如果你有一个这样的例子,我很乐意看到它 .

    如何实现一个函数,该函数将使用半对偶语义对任何浮点值进行舍入?

    正如评论中提到的那样,您可以确保设置默认舍入模式并使用 rint (或 lrint ,因为它听起来您实际上想要一个整数结果),或者您可以通过调用 round 然后修复来实现自己的舍入功能像 gnasher729 建议的中途病例 . 一旦采用了针对C的n1778绑定,您将能够使用 roundevenfromfp 函数执行此操作,而无需控制舍入模式 .

  • 5

    使用C标准库中的 remainder(double x, 1.0) . 这与当前的舍入模式无关 .

    余数函数计算IEC要求的余数x REM y60559

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

    并利用添加的自然舍入行为,而不必使用任何其他“舍入到整数”设施 .

  • 0

    以下是 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 implementation:

    temp = (number)     
    rounded_number = int( round(-1+ temp%2)*0.00001 + temp )
    

    C++ implementation: (使用math.h作为楼层功能)

    float temp = (number)     
    int rounded_number = (int)( (-1+ temp%2)*0.00001 + temp + 0.5)
    

    这将给出的输出如下 . 标准:

    (3.5) - > 4(2.5) - > 2


    Edit 1 : 正如@Mark Dickinson在评论中指出的那样 . 可以根据代码中的要求修改错误以使其标准化 . 对于python,要将其转换为可能的最小浮点值,您可以执行以下操作 .

    import sys
    error = sys.float_info.min
    

相关问题