昨天我问question我为什么在浮点运算中失去准确性。我收到一个关于如何在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
时,我们从较低的精度值中得到正确的答案。当然,如果较低的精度值可以给出正确的答案,那么更高的精度值也应该给出正确的答案吗?这与“双舍入”有关吗?
先谢谢,这对我来说是一个全新的主题,所以我有点挣扎。
答案 0 :(得分: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-次正规数的有效数也比IEEE754表示更多。因此,计算结果在x87中比IEEE754中有一个更重要的位。