3

该程序在第一个命令行参数上调用 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 是可表示的,我认为这个问题与“将 C 字符串转换为 / 从双精度数转换时的奇怪行为”不同,其中转换的值已下溢。

  2. 在 SUSE 上,如果我使用参数运行程序,2.22507385850720138310e-308则 errno 设置为 0。(如果我使用参数运行程序,0x1p-1022则 errno 也设置为 0。)

4

2 回答 2

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是实现定义的。

于 2017-02-02T15:10:28.370 回答
3

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

strtod()参考7.12.1:

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

虽然,从数学上讲,输入字符串小于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而不是考虑边缘条件。

于 2017-02-02T15:52:37.237 回答