浮点数精度再论

5

昨天我在Stack Overflow上询问了一个问题,关于为什么我在浮点运算中失去了精度。我得到了一个答案,说这是由于中间结果被保存在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时,我们从较低精度的值中得到了正确的答案。如果较低精度的值可以给出正确的答案,那么较高精度的值也应该给出正确的答案,对吗?这与“双舍入”有关吗?
谢谢您提前帮忙,这对我来说是一个全新的主题,所以我有点困难。

以下内容可能是正确的,但对我来说并不明显:如果较低精度的值可以给出正确的答案,那么较高精度的值也应该能够给出正确的答案吧? - NPE
如果我是你,我会在这种计算中使用“long double”... - Rontogiannis Aristofanis
我们怎么知道低精度数字在最后一位上不是随机值?总有10%的概率命中预期值。 - Bo Persson
@RondogiannisAristophanes 我的愿望是理解正在发生的事情。 - john
@BoPersson 您的评论让我感到困惑,没有随机数字,一切都是确定的。此外,IEEE-754浮点数是二进制而不是十进制。 - john
显示剩余2条评论
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)

现在,当使用IEEE754表示计算1.6*tiny时,它会被舍入为最接近数学结果的可表示数字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-次正常数的有效数字在x87中具有更多的位数。因此,这里x87计算的结果比IEEE754多一个有效位。


谢谢Daniel,一个工作示例确实是我需要的。因此,当1.b3cccaa7b72e1×2^(-1023)被转换回IEEE-754时,它会向下舍入为0.d9e66553db970×2^(-1022),而不是向上舍入为0.d9e66553db971×2^(-1022)?一般情况下,这个操作的舍入模式是什么? - john
没错。(虽然我不知道 printf 是否会将其四舍五入为 IEEE754,printf 也可能使用 x87 表示法。)IEEE754 中的默认舍入模式是 round-ties-to-even,即尾数的最后一位为零。 - Daniel Fischer
1
嗨,丹尼尔,有一个小注释:你描述x87中的加法方式时,接近“由于有效位数的限制,它变成了0.0000000000003×2^(-1023)”听起来像是Cray加法(http://cs.nyu.edu/courses/fall03/G22.2420-001/lec4.pdf)。相反,x87所做的是概念上计算精确的和(1.b3cccaa7b72e1333333333334×2^(-1023)),然后四舍五入。 - Pascal Cuoq
@PascalCuoq 谢谢,我不确定在那种配置下x87是如何工作的。 - Daniel Fischer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接