C语言 符合IEEE-754标准的舍入半偶数

nhhxz33t  于 2023-08-03  发布在  其他
关注(0)|答案(6)|浏览(103)

C标准库在C99中提供了roundlroundllround系列函数。然而,这些函数不符合IEEE-754,因为它们没有实现IEEE所要求的半到偶数的“银行舍入”。如果小数部分正好为0.5,则半偶数舍入要求将结果舍入到最接近的偶数值。相反,C99标准要求使用与零相差一半的值,如cppreference.com上所示
1-3)计算最接近arg的整数值(浮点格式),将大小写从零舍入一半,而不考虑目前的舍入模式。
在C语言中实现舍入的通常的特别方法是表达式(int)(x + 0.5f),尽管在严格的IEEE-754数学中是incorrect,但通常由编译器翻译成正确的cvtss2si指令。然而,这肯定不是一个可移植的假设。
我如何实现一个函数,将舍入任何浮点值与半到偶数的语义?如果可能的话,函数应该只依赖于语言和标准库语义,这样它就可以在非IEEE浮点类型上操作。如果这是不可能的,则根据IEEE-754位表示定义的答案也是可接受的。请用<limits.h><limits>来表征任何常数。

juud5qan

juud5qan1#

C标准库在C99中提供了roundlroundllround系列函数。但是,这些函数不符合IEEE-754,因为它们不实现IEEE...
讨论某个函数是否“符合IEEE-754”是没有意义的。IEEE-754兼容性要求具有定义的语义的一组数据类型操作是可用的。它不要求这些类型或操作具有特定的名称,也不要求 * 仅 * 这些操作可用。一个实现可以提供它想要的任何附加功能,并且仍然是兼容的。如果一个实现想要提供舍入到奇数、随机舍入、远离零的舍入和不精确时的陷阱,它可以这样做。
IEEE-754实际上对舍入的要求是提供以下六种操作:

转换为整数阶转换为偶数阶(x)
向零方向转换为整数(x)
转换为正整数(x)
向负整数转换(x)
转换为整数等待时间(x)
转换为整数精确值(x)

在C和C中,这些运算的最后五个分别绑定到truncceilfloorroundrint函数。C11和C14对于第一个版本没有绑定,但未来的版本将使用roundeven。如您所见,round实际上是必需的操作之一。
但是,roundeven在当前的实现中不可用,这就引出了您问题的下一部分:
在C语言中实现舍入的通常的特别方法是表达式(int)(x + 0.5f),尽管在严格的IEEE-754数学中是不正确的,但通常由编译器翻译成正确的cvtss2si指令。然而,这肯定不是一个可移植的假设。
该表达式的问题远远超出了“严格的IEEE-754数学”。它对于负数x是完全不正确的,对于nextDown(0.5)给出了错误的答案,并将223 binade中的所有奇数都变成了偶数。任何将其翻译成cvtss2si的编译器都是可怕的,可怕的坏掉了。如果你有这样的例子,我很乐意看到。
我如何实现一个函数,将舍入任何浮点值与半到偶数的语义?
正如
njuffa在注解中所指出的,您可以确保设置了默认的舍入模式,并使用rint(或lrint,因为听起来您实际上想要一个整数结果),或者您可以通过调用round来实现您自己的舍入函数,然后像gnasher 729**建议的那样修正中途情况。一旦采用了C语言的n1778绑定,就可以使用roundevenfromfp函数来执行此操作,而无需控制舍入模式。

vom3gejh

vom3gejh2#

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

fcg9iug3

fcg9iug33#

对数字x进行舍入,如果x和round(x)之间的差正好是+0.5或-0.5,并且round(x)是奇数,则round(x)的舍入方向错误,因此要从x中减去差值。

amrnrhlw

amrnrhlw4#

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

字符串
并且利用加法的自然舍入行为,而不必使用任何其它“舍入到整数”工具。

mzmfm0qo

mzmfm0qo5#

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

iugsix8n

iugsix8n6#

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

**逻辑:**error = 0.00001

1.数量=2.5

  1. temp = floor(2.5)%2 = 2%2 = 0
    1.x= -1 + temp = -1
  2. x*error + number = 2.40009
  3. round(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++实现:(floor函数使用 math.h

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


这将给予的输出将如下acc。标准:
(3.5)-> 4
(2.5)-> 2

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

import sys
error = sys.float_info.min

相关问题