8

我无法理解该程序的输出

int main()
{
    double x = 1.8939201459282359e-308;
    double y = 4.9406564584124654e-324;
    printf("%23.16e\n", 1.6*y);
    printf("%23.16e\n", 1.7*y);
    printf("%23.16e\n", 1.8*y);
    printf("%23.16e\n", 1.9*y);
    printf("%23.16e\n", 2.0*y);
    printf("%23.16e\n", x + 1.6*y);
    printf("%23.16e\n", x + 1.7*y);
    printf("%23.16e\n", x + 1.8*y);
    printf("%23.16e\n", x + 1.9*y);
    printf("%23.16e\n", x + 2.0*y);
}

输出是

9.8813129168249309e-324
9.8813129168249309e-324
9.8813129168249309e-324
9.8813129168249309e-324
9.8813129168249309e-324
1.8939201459282364e-308
1.8939201459282364e-308
1.8939201459282369e-308
1.8939201459282369e-308
1.8939201459282369e-308

我正在使用 IEEE 算法。该变量y包含最小的 IEEE 编号。前五张照片显示的数字是我预期的两倍。令我困惑的是,接下来的五张照片显示了不同的数字。如果1.6*y是一样的2.0*y那么怎么x + 1.6*y会不同x + 2.0*y呢?

4

1 回答 1

8

简而言之

你说你的编译器是 Visual C++ 2010 Express。我无权访问此编译器,但我知道它生成的程序最初将 x87 CPU 配置为使用 53 位精度,以便尽可能模拟 IEEE 754 双精度计算。

不幸的是,“尽可能接近”并不总是足够接近。为了模拟双精度,历史 80 位浮点寄存器的有效位宽度可能受到限制,但它们始终保留完整的指数范围。在操作非规范化(例如您的 )时,差异尤其明显y

怎么了

我的解释是 in printf("%23.16e\n", 1.6*y);,1.6*y被计算为 80 位减少有效位的全指数数(因此它是一个普通数),然后转换为 IEEE 754 双精度(导致非正规),然后打印。

另一方面, in printf("%23.16e\n", x + 1.6*y);,x + 1.6*y使用所有 80 位缩减有效位和全指数数字(同样所有中间结果都是正常数字)计算,然后转换为 IEEE 754 双精度,然后打印。

这可以解释为什么1.6*y打印与2.0*y添加到x. 打印的数字是双精度非正规数。添加到的数字x是一个 80 位减少有效位和全指数正常数(不是同一个)。

生成 x87 指令时其他编译器会发生什么

其他编译器,如 GCC,不配置 x87 FPU 来操作 53 位有效位。这可能产生相同的结果(在这种情况下x + 1.6*y,将使用所有 80 位全有效位和全指数计算,然后转换为双精度以打印或存储在内存中)。在这种情况下,这个问题更加明显(您不需要涉及非正规数或无限数来注意到差异)。

David Monniaux 的这篇文章包含您可能希望的所有详细信息以及更多信息。

删除不需要的行为

为了摆脱这个问题(如果你认为它是一个问题),找到告诉你的编译器为浮点生成 SSE2 指令的标志。它们完全实现了单精度和双精度的 IEEE 754 语义。

于 2013-03-15T20:36:51.943 回答