双精度奇怪的行为。需要解释

时间:2012-05-08 19:46:48

标签: c gcc floating-point

以下是代码:

#include <stdio.h>
#include <math.h>

static double const x = 665857;
static double const y = 470832;

int main(){
    double z = x*x*x*x -(y*y*y*y*4+y*y*4);
    printf("%f \n",z);
    return 0;
}

神秘地(对我而言)如果使用GCC 4.6在32位计算机上编译(或者在我的情况下使用64位计算机上的-m32标志),则此代码打印“0.0”。据我所知,浮点运算可能会溢出/下溢它们或者它们会失去精度,但是...... 0?怎么样?

提前致谢。

4 个答案:

答案 0 :(得分:7)

问题不在于数字溢出。问题是双精度没有足够的精度来区分减法的两个操作数。

x*x*x*x的值是196573006004558194713601。

y*y*y*y*4+y*y*4的值为196573006004558194713600。

这些数字有78位,只有最后一位不同。双精度数字只有53位。其他数字四舍五入到只有53位。

在您的情况下,两个操作数四舍五入为相同的数字,因此它们的差值为0.

如果你稍微重写z的表达式,就会发生更奇怪的事情:

double z = x * x * x * x - ((y * y + 1) * y * y * 4);

通过此更改,您将获得33554432!为什么?因为舍入中间结果的方式导致右操作数的最后一位不同。最后一位的值是2 ^(78-53)= 2 ^ 25。

答案 1 :(得分:6)

使用任意精度整数计算表达式:

Prelude> 665857^4 - 4*(470832^4 + 470832^2)
1

由于double通常只有53位精度而中间结果有78位,因此精度不足以精确计算结果,因此它是四舍五入的,最后一位在某些时候被遗忘

答案 2 :(得分:4)

代码中没有浮点溢出或下溢。这两个量的大小为1.96573006×10 ^ 23,并且很大程度上适合double。你的例子简单地说明了灾难性的取消,你减去两个接近的数量,结果的相对精确度变得很糟糕。

请参阅http://en.wikipedia.org/wiki/Loss_of_significance

答案 3 :(得分:2)

这是IEEE 754以标准化形式表示浮点数的方式的结果。 float或double或其他符合IEEE 754标准的表示形式如:

1.xxxxxxxxxxxxxxxxxxx * 2^exp

其中xxxxxxxxxxxxxxxxxxx是尾数的小数部分,因此尾数本身始终在[1,2]范围内。始终为1的整数部分不存储在表示中。 x位的数量定义精度。 double为52位。指数以偏移形式存储(必须减去1023以获得其值),但现在无关紧要。

64位IEEE 754中的

665857 ^ 4是:

0 10001001100 (1)0100110100000001111100111011101010000101110010100010
+ exponent    mantissa

(第一位是符号位:0 =正,1 - 负;括号中的位未真正存储)

在80位x86扩展精度中,它是:

0 10001001100    (1)0100110100000001111100111011101010000101110010100010
0 100000001001100 1 010011010000000111110011101110101000010111001010000111000111011

(这里整数部分明确是表示的一部分 - 与IEEE 754的偏差;为了清晰起见,我已经对齐了尾数)

64位IEEE 754和80位x86扩展精度的

4 * 470832 ^ 4是:

0 10001001100    (1)0100110100000001111100111011101001111111010101100111
0 100000001001100 1 010011010000000111110011101110100111111101010110011100100010000
64位IEEE 754和80位x86扩展精度的

4 * 470832 ^ 2是:

0 10000100110    (1)1001110011101010100101010100100000000000000000000000
0 100000000100110 1 100111001110101010010101010010000000000000000000000000000000000

当您总结最后两个数字时,过程如下:较小的值调整其指数以匹配较大值的指数,而尾数向右移动以保留该值。由于两个指数相差38,较小数字的尾数向右移38位:

470832 ^ 2 * 4,调整后的64位IEEE 754和80位x86扩展精度:

 this bit came from 1.xxxx ------------------------------v
0 10001001100    (0)0000000000000000000000000000000000000110011100111010|1010
0 100000001001100 0 0000000000000000000000000000000000000110011100111010101001010101

现在两个数量都有相同的指数,它们的尾数可以相加:

0 10001001100 (1)0100110100000001111100111011101001111111010101100111|0010
0 10001001100 (0)0000000000000000000000000000000000000110011100111010|1010
--------------------------------------------------------------------------
0 10001001100 (1)0100110100000001111100111011101010000101110010100001|1100

我在条形图右侧保留了一些80位精度位,因为内部求和以80位的更高精度完成。

现在让我们在64位+ 80位代表的某些位执行减法:

minuend    0 10001001100 (1)0100110100000001111100111011101010000101110010100001|1100
subtrahend 0 10001001100 (1)0100110100000001111100111011101010000101110010100001|1100
-------------------------------------------------------------------------------------
difference 0 10001001100 (0)0000000000000000000000000000000000000000000000000000|0000

一个纯粹的0!如果以完整的80位执行计算,您将再次获得纯0。

这里真正的问题是1.0不能用64位精度表示,指数为2 ^ 77--尾数中没有77位精度。对于80位精度也是如此 - 尾数中只有63位,比指数为2 ^ 77时代表1.0所需的小14位。

就是这样!这就是科学计算的奇妙世界,没有什么能像你在数学课上那样有所作为......