5

昨天我问了一个问题,为什么我在浮点运算中失去了准确性。由于中间结果保存在 x87 寄存器中,我收到了一个答案。这很有帮助,但一些细节仍然让我无法理解。这是我在上一个问题中提出的程序的一个变体,我在调试模式下使用 VC++ 2010 Express。

int main()
{
    double x = 1.8939201459282359e-308; /* subnormal number */
    double tiny = 4.9406564584124654e-324; /* smallest IEEE double */
    double scale = 1.6;
    double temp = scale*tiny;
    printf("%23.16e\n", x + temp);
    printf("%23.16e\n", x + scale*tiny);
}

这输出

1.8939201459282369e-308
1.8939201459282364e-308

根据 IEEE 标准,第一个值是正确的。给scale变量一个值 2.0 给出了两个计算的正确值。我知道temp在第一次计算中是一个低于正常值的值,因此会失去精度。我也知道 的值scale*tiny保存在具有更大指数范围的 x87 寄存器中,因此该值的精度高于temp. 我不明白的是,当将值添加到x我们从较低精度值中得到正确答案时。当然,如果较低的精度值可以给出正确的答案,那么较高的精度值也应该给出正确的答案吗?这与“双舍入”有关吗?

在此先感谢,这对我来说是一个全新的主题,所以我有点挣扎。

4

1 回答 1

7

关键是由于较大的指数范围,这两个数字在 x87 表示中不是次正规的。

在 IEEE754 表示中,

x    = 0.d9e66553db96f × 2^(-1022)
tiny = 0.0000000000001 × 2^(-1022)

但在 x87 表示中,

x    = 1.b3cccaa7b72de × 2^(-1023)
tiny = 1.0000000000000 × 2^(-1074)

现在,当1.6*tiny在 IEEE754 表示中计算时,它被四舍五入,0.0000000000002 × 2^(-1022)因为这是最接近数学结果的可表示数字。将其添加到x结果中

  0.d9e66553db96f × 2^(-1022)
+ 0.0000000000002 × 2^(-1022)
-----------------------------
  0.d9e66553db971 × 2^(-1022)

但在 x87 表示中,1.6*tiny变为

1.999999999999a × 2^(-1074)

当它被添加时

  1.b3cccaa7b72de × 2^(-1023)
+ 0.0000000000003333333333334 × 2^(-1023)
-----------------------------------------
  1.b3cccaa7b72e1333333333334 × 2^(-1023)

四舍五入到 53 个有效位的结果是

  1.b3cccaa7b72e1 × 2^(-1023)

有效数字中的最后一位为 1。如果然后将其转换为 IEEE754 表示(有效数字中最多可以有 52 位,因为它是次正规数),因为它正好是两个相邻可表示数字之间的一半,0.d9e66553db970 × 2^(-1022)并且0.d9e66553db971 × 2^(-1022)它是默认情况下,四舍五入到有效数为零的最后一位。

请注意,如果 FPU 未配置为仅使用 53 位作为有效位,而是使用 x87 扩展精度类型的完整 64 位,则加法的结果将更接近 IEEE754 结果0.d9e66553db971 × 2^(-1022),因此四舍五入。

实际上,由于 x87 表示具有更大的指数范围,因此即使有效位中的位数有限,IEEE754 次正规数的有效位也比 IEEE754 表示中的位多。因此,计算结果在 x87 中比在 IEEE754 中多一位。

于 2013-03-16T15:53:42.663 回答