11

C 标准库提供 C99 中的roundlroundllround系列函数。但是,这些函数不符合 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>

4

6 回答 6

8

C 标准库提供 C99 中的roundlroundllround系列函数。但是,这些函数不符合 IEEE-754 标准,因为它们没有按照 IEEE 的要求实现半对偶的“银行家四舍五入”……

谈论单个功能是否“符合 IEEE-754”是没有意义的。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 中实现舍入的常用特别方法是表达式(int)(x + 0.5f),尽管在严格的 IEEE-754 数学中不正确,但通常由编译器翻译成正确的cvtss2si指令。然而,这当然不是一个可移植的假设。

该表达式的问题远远超出了“严格的 IEEE-754 数学”。负数是完全不正确的x,给出了错误的答案nextDown(0.5),并将 2**23 binade 中的所有奇数转换为偶数。任何将其翻译成的编译器cvtss2si都非常糟糕。如果您有发生这种情况的示例,我很乐意看到它。

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

正如njuffa在评论中指出的那样,您可以确保设置和使用默认舍入模式rint(或者lrint,听起来您实际上想要一个整数结果),或者您可以通过调用round然后修复中途情况来实现自己的舍入函数就像gnasher729建议的那样。一旦采用了 C 的 n1778 绑定,您就可以使用roundevenorfromfp函数来执行此操作,而无需控制舍入模式。

于 2015-09-24T10:49:12.843 回答
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;
}

测试代码

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 
于 2015-09-23T23:32:13.450 回答
6

将数字 x 舍入,如果 x 和舍入 (x) 之间的差值正好是 +0.5 或 -0.5,并且舍入 (x) 是奇数,则舍入 (x) 的舍入方向错误,因此您减去差值X。

于 2015-09-23T18:13:27.863 回答
2

从 C++11 开始,STL 中有一个函数可以进行半偶数舍入。如果浮点舍入模式设置为FE_TONEAREST(默认),那么std::nearbyint就可以了。

std::nearbyint 的 C++ 参考

于 2019-07-15T19:48:20.077 回答
2

数据类型可以表示 8388608.0f 到 16777216.0f 范围内的float所有整数,但不能表示分数。任何float大于 8388607.5f 的数字都是整数,不需要四舍五入。将 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;
}

并利用加法的自然舍入行为,而无需使用任何其他“舍入到整数”工具。

于 2015-09-25T22:44:41.973 回答
1

以下是遵循 IEEE 舍入标准的舍入到偶数程序的简单实现。

逻辑: 错误 = 0.00001

  1. 数字=2.5
  2. 温度 = 地板(2.5)%2 = 2%2 = 0
  3. x = -1 + 温度 = -1
  4. x*错误 + 数字 = 2.40009
  5. 回合(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作为 floor 函数)

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

这将给出的输出如下所示。符合标准:

(3.5) -> 4

(2.5) -> 2


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

import sys
error = sys.float_info.min
于 2017-12-23T08:07:15.373 回答