什么时候发生下溢?

zel*_*ell 15 c floating-point floating-point-exceptions underflow

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

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

换句话说,如果我们计算出x/y其中两个xydouble,那么应该发生溢如果0 < |x/y| < 2.2251e-308(最小正正规化double2.2251e-308).从理论上说,因此,无论是1.77e-308/101.777e-308/10应触发溢异常.该理论与我在下面的C程序中测试的内容相矛盾.

#include <stdio.h>
#include <fenv.h>
#include <math.h>


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");
}
Run Code Online (Sandbox Code Playgroud)

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

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

chu*_*ica 9

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

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

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

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

#include <float.h>
#include <stdio.h>
#include <fenv.h>
#include <math.h>

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;
}
Run Code Online (Sandbox Code Playgroud)

产量

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
Run Code Online (Sandbox Code Playgroud)

  • 很好的解释!谢谢你。 (2认同)