为什么带有DBL_MIN的strtod()会给出ERANGE?

时间:2017-02-02 14:58:36

标签: c floating-point strtod

该程序在第一个命令行参数上调用strtod()并打印返回的值:

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <float.h>

int main(int argc, char **argv)
{
  errno = 0;
  double d = strtod(argv[1], NULL);
  int errno_sav = errno;

  printf("string = %s\n", argv[1]);
  printf("d = %.*e = %a\n", DBL_DIG + 2, d, d);
  printf("errno = %d\n", errno_sav);
  printf("DBL_MIN = %.*e = %a\n", DBL_DIG + 2, DBL_MIN, DBL_MIN);
  return 0;
}

当我在SUSE Linux Enterprise Server 11 SP2或Linux Mint 17 Qiana上运行参数2.22507385850720138309e-308时,对应于最小的可表示的 1 双值(DBL_MIN),它给出了我期望的输出:

string = 2.22507385850720138309e-308
d = 2.22507385850720138e-308 = 0x1p-1022
errno = 0
DBL_MIN = 2.22507385850720138e-308 = 0x1p-1022

但是,在具有相同参数 2 的SUSE Linux Enterprise Server 11 SP3上,errno设置为ERANGE:

string = 2.22507385850720138309e-308
d = 2.22507385850720138e-308 = 0x1p-1022
errno = 34
DBL_MIN = 2.22507385850720138e-308 = 0x1p-1022

第二种行为是否有效,如果是,为什么?

脚注:

  1. 由于DBL_MIN是可表示的,我认为这个问题不同于“Odd behavior when converting C strings to/from doubles”,其中转换的价值已经下降。

  2. 在SUSE上,如果我运行参数为2.22507385850720138310e-308的程序,则将errno设置为0.(如果我运行参数为0x1p-1022的程序,则还会设置errno到0。)

2 个答案:

答案 0 :(得分:3)

输入值2.22507385850720138309e-308小于DBL_MIN的精确值(2 -1022 )。较大的十进制扩展是:

2.225073858507201383090232717332404064219215980462331830553327416887204434813918...e-308 v.
2.22507385850720138309e-308 

从技术上讲,这会导致下溢。

C 1999(7.20.1.3)指出在这种情况下:

  

如果结果下溢(7.12.1),则函数返回一个值,该值的大小不大于返回类型中的最小归一化正数; errno获取值ERANGE是否为实现定义。

答案 1 :(得分:3)

当我阅读C规范时,这是不正确的行为。

strtod()指的是7.12.1:

  

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

虽然在数学上,输入字符串小于DBL_MIN,...

text
2.22507385850720_138e-308       // later post
2.22507385850720_138309e-308    // original post
DBL_MIN
2.22507385850720_13830902...e-308 

...转换为(double) DBL_MIN并非出现非常的舍入错误。如果不设置DBL_MIN,则值应转换为errno

作为参考,会显示附近的其他double

2.22507385850720_08890245...e-308  nextafter(DBL_MIN, 0.0),if sub-normals allowed, else 0.0
2.22507385850720_13830902...e-308  DBL_MIN
2.22507385850720_18771558...e-308  nextafter(DBL_MIN, 1.0)

我怀疑底层代码使用long double之类的扩展精度执行转换,然后测试long double结果是否小于DBL_MIN而不是考虑边缘条件。