为什么 C++ strtod 将“708530856168225829.3221614e9”解析为 7.08530856168225898e+26 而不是 7.08530856168225761e+26?

Why C++ strtod parses "708530856168225829.3221614e9" to 7.08530856168225898e+26 instead of 7.08530856168225761e+26?

在编写自定义浮点数解析器(出于速度原因)并根据 strtod 检查精度(我认为它非常准确)时,我发现有时使用

 number = (int_part + dec_part/pow(10., no_of_decs)) * pow(10., expo)

似乎实际上比 strtod 结果“更准确”(当使用 long double 完成计算然后将结果转换回双精度时),这令人惊讶。

官方 IEEE754 解析规则是否真的强制要求不太准确的结果?

例如字符串

 708530856168225829.3221614e9

天真的计算给出

 7.08530856168225761e+26

这似乎比 strtod

的结果更接近
 7.08530856168225898e+26

“理论”结果(无法用 64 位 double 准确表示)

 7.085308561682258293221614e+26

(实验是在 Arch linux 上用 g++ (GCC) 10.2.0clang++ 11.1.0 完成的,他们都同意 strtod 和 [=23 的 ...898e+26 =] 用于简单计算)

如您所见,7.085308561682258293221614e+26 在 IEEE-754 双精度 (binary64) 中不可表示。因此,它不是候选结果,对结果没有决定性作用。

最接近 708530856168225829.3221614e9 的两个可表示 binary64 的数字是 708530856168225760595673088 和 708530856168225898034626560。将原件完整写出并在中间与原件对齐以供检查=14,我们有:

708530856168225760595673088   representable value below original
708530856168225829322161400   original number
708530856168225898034626560   representable value above original

减法给出了较低和原始之间以及原始和较高之间的绝对差异:

                68726488312   distance to lower
                68712465160   distance to higher

因此,更高的数字 708530856168225898034626560 更接近原始数字。这实际上是您报告的结果,因此软件运行正常。

请注意,将 binary64 视为没有所有有效数字的十进制是错误的。像上面写完整数字一样写出部分十进制数字,我们有:

7.08530856168225761e+26         proposed result
7 08530856168225829.3221614e9   original number
7.08530856168225898e+26         reported result of strtod

差异:

                 68322161400   distance to lower
                 68677838600   distance to higher

因此,将浮点数的实际值四舍五入为没有所有数字的十进制数字会引入错误并描绘出不正确的值。二进制浮点数 不是 并且 不代表 十进制数字,并且在没有所有有效数字的情况下显示它们显示不正确的值。

值708530856168225829.3221614e9介于2double.

之间
7.08530856168225 760 59567309...e+26 // lower double
7.08530856168225 829 31514982...e+26 // half way
7 08530856168225 829.3221614e9       // OP's code
7.08530856168225 898 03462656...e+26 // upper double
1 23456789012345 678 90              // Significant digit count  

非常 几乎是这两个 double 的一半。

在这种情况下,我说 strtod() 中的 7.08530856168225 898 03462656...e+26 是更好的答案,而 OP 的 朴素计算 较差,并且由于累积除法、乘法和加法注入的舍入误差。


注意:IEEE754解析在解析文本时不要求无限精度。要求使用至少 N+3位有效小数位。 (我相信 N==17) binary64 又名 double.

当使用截断的文本进行转换时,答案可能与在接近一半的情况下使用更多数字不同。尽管如此,在这种情况下,即使截断到 20 位数字,上面的 double 也是更好的选择。