以下是代码:
#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?怎么样?
提前致谢。
答案 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
。你的例子简单地说明了灾难性的取消,你减去两个接近的数量,结果的相对精确度变得很糟糕。
答案 3 :(得分:2)
这是IEEE 754以标准化形式表示浮点数的方式的结果。 float或double或其他符合IEEE 754标准的表示形式如:
1.xxxxxxxxxxxxxxxxxxx * 2^exp
其中xxxxxxxxxxxxxxxxxxx
是尾数的小数部分,因此尾数本身始终在[1,2]范围内。始终为1的整数部分不存储在表示中。 x
位的数量定义精度。 double
为52位。指数以偏移形式存储(必须减去1023以获得其值),但现在无关紧要。
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位。
就是这样!这就是科学计算的奇妙世界,没有什么能像你在数学课上那样有所作为......