什么时候发生下溢?

时间:2017-02-16 14:44:18

标签: c floating-point underflow floating-point-exceptions

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

  

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

换句话说,如果我们计算x/y xy都是double的{​​{1}},那么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");
}

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

[编辑]我通过this online IDE分享了上述代码。

2 个答案:

答案 0 :(得分:9)

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

  

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

1.777e-308,转换为最接近的binary64 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;
}

输出

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

答案 1 :(得分:1)

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

  

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

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

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