什么时候发生下溢?

我遇到计算1.77e-308/10触发下溢exception的情况,但计算1.777e-308/10则没有。 这很奇怪,因为:

当浮点运算的真实结果的幅度(​​即,接近于零)小于目标数据类型中可表示为正常浮点数的最小值(来自算术下溢,维基百科)时,会发生下溢

换句话说,如果我们计算x/yy都是double x/y ,那么如果0 < |x/y| < 2.2251e-308则会发生下溢0 < |x/y| < 2.2251e-308 0 < |x/y| < 2.2251e-308 (最小的正标准化double 2.2251e-3082.2251e-308 )。 因此,理论上, 1.77e-308/101.777e-308/10都应触发下溢exception。 该理论与我在下面的C程序中测试的内容相矛盾。

 #include  #include  #include  int main(){ double x,y; // x = 1.77e-308 => underflow // x = 1.777e-308 gives ==> no underflow x=1.77e-308; feclearexcept(FE_ALL_EXCEPT); y=x/10.0; if (fetestexcept(FE_UNDERFLOW)) { puts("Underflow\n"); } else puts("No underflow\n"); } 

为了编译程序,我使用了gcc program.c -lm ; 我也尝试过Clang,它给了我相同的结果。 任何解释?

[编辑]我通过这个在线IDE分享了上面的代码。

下溢不仅是范围的问题,也是精确/舍入的问题。

7.12.1错误条件的处理
如果数学结果的大小如此之小以至于在指定类型的对象中无法表示数学结果而没有非常的舍入误差,则结果下溢。 C11§7.12.16

1.777e-308,转换为最近的二进制64 0x1.98e566222bcfcp-1023,碰巧有一个有效数字(0x198E566222BCFC,7193376082541820)是10的倍数。所以除以10是准确的。 没有舍入错误。

我发现使用hex表示法更容易演示。 请注意,除最小值外,除以2总是精确的。

 #include  #include  #include  #include  int uf_test(double x, double denominator){ printf("%.17e %24a ", x, x); feclearexcept(FE_ALL_EXCEPT); double y=x/denominator; int uf = !!fetestexcept(FE_UNDERFLOW); printf("%-24a %s\n", y, uf ? "Underflow" : ""); return uf; } int main(void) { uf_test(DBL_MIN, 2.0); uf_test(1.777e-308, 2.0); uf_test(1.77e-308, 2.0); uf_test(DBL_TRUE_MIN, 2.0); uf_test(pow(2.0, -1000), 10.0); uf_test(DBL_MIN, 10.0); uf_test(1.777e-308, 10.0); uf_test(1.77e-308, 10.0); uf_test(DBL_TRUE_MIN, 10.0); return 0; } 

产量

 2.22507385850720138e-308 0x1p-1022 0x1p-1023 1.77700000000000015e-308 0x1.98e566222bcfcp-1023 0x1.98e566222bcfcp-1024 1.77000000000000003e-308 0x1.97490d21e478cp-1023 0x1.97490d21e478cp-1024 4.94065645841246544e-324 0x1p-1074 0x0p+0 Underflow // No underflow as inexact result is not too small 9.33263618503218879e-302 0x1p-1000 0x1.999999999999ap-1004 // Underflow as result is too small and inexact 2.22507385850720138e-308 0x1p-1022 0x1.99999999999ap-1026 Underflow // No underflow as result is exact 1.77700000000000015e-308 0x1.98e566222bcfcp-1023 0x1.471deb4e8973p-1026 1.77000000000000003e-308 0x1.97490d21e478cp-1023 0x1.45d40a818394p-1026 Underflow 4.94065645841246544e-324 0x1p-1074 0x0p+0 Underflow 

检查您调用的函数的文档,导致定义:

FE_UNDERFLOW早期浮点运算的结果是低于正常的,精度损失

http://en.cppreference.com/w/c/numeric/fenv/FE_exceptions

我想你已经证实你的号码是不正常的。 测试还包括精度损失。 如果您打印更重要的数字,您会发现报告溢出的数字似乎在大约16位小数处失去了精确度。 我不清楚在一个次正规数上会有多少重要的数字,但我认为这一定是你的答案。