为什么 floor, ceil implementation return x + x when x is NaN or inf?
why floor, ceil implementation return x + x when x is NaN or inf?
我正在阅读 IEEE-754 数学函数在 glibc 中的实现。
这是 floor
实现。
float
__floorf(float x)
{
int32_t i0,j0;
uint32_t i;
GET_FLOAT_WORD(i0,x);
j0 = ((i0>>23)&0xff)-0x7f;
if(j0<23) {
if(j0<0) {
/* return 0*sign(x) if |x|<1 */
if(i0>=0) {i0=0;}
else if((i0&0x7fffffff)!=0)
{ i0=0xbf800000;}
} else {
i = (0x007fffff)>>j0;
if((i0&i)==0) return x; /* x is integral */
if(i0<0) i0 += (0x00800000)>>j0;
i0 &= (~i);
}
} else {
if(__builtin_expect(j0==0x80, 0)) return x+x; /* inf or NaN */
else return x; /* x is integral */
}
SET_FLOAT_WORD(x,i0);
return x;
}
有趣的部分是 if(__builtin_expect(j0==0x80, 0)) return x+x; /* inf or NaN */
。
为什么 return x+x
当 x
是 inf 或 NaN 时?
为什么不只是 return x
?
编辑
我的代码来自 https://github.com/lattera/glibc/blob/895ef79e04a953cac1493863bcae29ad85657ee1/sysdeps/ieee754/flt-32/s_floorf.c,并假设它是 glibc 的分支。
我不确定,但我相信它与异常处理有关:如果 x
太大,那么 return x+x
可能会产生异常。虽然只是 return x
根本不会导致任何异常,并且您可能认为一切正常,但实际上您正在使用错误的数字,因为它们超出了正常的编号范围(inf
), 或者因为他们根本就错了 (NaN
).
目的是引发异常。当 floor
的输入是信号 NaN 时,例程应引发浮点 无效操作 异常。1 而不是调用一些通过操作浮点状态寄存器中的位来执行此操作的例程,简单地评估 x+x
更容易,因为向其自身(或任何东西)添加信号 NaN 将引发 无效操作异常。
这在数学库例程的实现中很常见。再举一个例子,考虑 sin(x)
。对于非常小的 x
值,sin(x)
非常接近 x
,以至于 x
是浮点格式中可表示的最接近的值,因此 returned值应为 x
。但是精确的数学罪 x 并不完全是 x
(如果 x
不为零),所以 inexact 异常应该提高。为此,例程可以 return,例如 x + x*x
。当 x
非常小(但不为零)时,这将评估为与 x
相同但它会引发 invalid 异常。
注意这种情况下的额外好处:当 x
为零时,x + x*x
不会引发 inexact 异常。因此,该表达式适用于零和非常小的非零情况。因此,它不仅可以替代手动引发异常,还可以替代基于 x
是否为零的分支。这在这些表达中并不少见;它们是实现功能的有效方式。
脚注
1 浮点异常不是 C++ 异常。它们的处理方式取决于浮点环境的设置。最常见的是,它们只是简单地引发程序稍后可以检查的标志。但它们也可能导致改变程序执行的陷阱,如 C++ 异常。
我正在阅读 IEEE-754 数学函数在 glibc 中的实现。
这是 floor
实现。
float
__floorf(float x)
{
int32_t i0,j0;
uint32_t i;
GET_FLOAT_WORD(i0,x);
j0 = ((i0>>23)&0xff)-0x7f;
if(j0<23) {
if(j0<0) {
/* return 0*sign(x) if |x|<1 */
if(i0>=0) {i0=0;}
else if((i0&0x7fffffff)!=0)
{ i0=0xbf800000;}
} else {
i = (0x007fffff)>>j0;
if((i0&i)==0) return x; /* x is integral */
if(i0<0) i0 += (0x00800000)>>j0;
i0 &= (~i);
}
} else {
if(__builtin_expect(j0==0x80, 0)) return x+x; /* inf or NaN */
else return x; /* x is integral */
}
SET_FLOAT_WORD(x,i0);
return x;
}
有趣的部分是 if(__builtin_expect(j0==0x80, 0)) return x+x; /* inf or NaN */
。
为什么 return x+x
当 x
是 inf 或 NaN 时?
为什么不只是 return x
?
编辑
我的代码来自 https://github.com/lattera/glibc/blob/895ef79e04a953cac1493863bcae29ad85657ee1/sysdeps/ieee754/flt-32/s_floorf.c,并假设它是 glibc 的分支。
我不确定,但我相信它与异常处理有关:如果 x
太大,那么 return x+x
可能会产生异常。虽然只是 return x
根本不会导致任何异常,并且您可能认为一切正常,但实际上您正在使用错误的数字,因为它们超出了正常的编号范围(inf
), 或者因为他们根本就错了 (NaN
).
目的是引发异常。当 floor
的输入是信号 NaN 时,例程应引发浮点 无效操作 异常。1 而不是调用一些通过操作浮点状态寄存器中的位来执行此操作的例程,简单地评估 x+x
更容易,因为向其自身(或任何东西)添加信号 NaN 将引发 无效操作异常。
这在数学库例程的实现中很常见。再举一个例子,考虑 sin(x)
。对于非常小的 x
值,sin(x)
非常接近 x
,以至于 x
是浮点格式中可表示的最接近的值,因此 returned值应为 x
。但是精确的数学罪 x 并不完全是 x
(如果 x
不为零),所以 inexact 异常应该提高。为此,例程可以 return,例如 x + x*x
。当 x
非常小(但不为零)时,这将评估为与 x
相同但它会引发 invalid 异常。
注意这种情况下的额外好处:当 x
为零时,x + x*x
不会引发 inexact 异常。因此,该表达式适用于零和非常小的非零情况。因此,它不仅可以替代手动引发异常,还可以替代基于 x
是否为零的分支。这在这些表达中并不少见;它们是实现功能的有效方式。
脚注
1 浮点异常不是 C++ 异常。它们的处理方式取决于浮点环境的设置。最常见的是,它们只是简单地引发程序稍后可以检查的标志。但它们也可能导致改变程序执行的陷阱,如 C++ 异常。