4

这是代码:

#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;
}

神秘地(对我来说)如果在 32 位机器上编译(或者在我的例子中使用 -m32 标志在 64 位机器上),这段代码会打印“0.0”,并且使用 GCC 4.6。据我所知,浮点运算有可能使它们上溢/下溢或失去精度,但是...... 0?如何?

提前致谢。

4

4 回答 4

7

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

的值为x*x*x*x196573006004558194713601。

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

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

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

如果稍微改写 z 的表达式,甚至会发生更奇怪的事情:

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

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

于 2012-05-08T19:56:41.550 回答
6

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

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

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

于 2012-05-08T19:55:21.577 回答
4

您的代码中没有浮点上溢或下溢。这两个量的数量级为 1.96573006 × 10^23,并且在很大程度上适合double. 你的例子只是说明了灾难性的取消,你减去两个相近的量,结果的相对精度变得可怕。

http://en.wikipedia.org/wiki/Loss_of_significance

于 2012-05-08T19:55:19.187 回答
3

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

1.xxxxxxxxxxxxxxxxxxx * 2^exp

其中xxxxxxxxxxxxxxxxxxx是尾数的小数部分,因此尾数本身始终在 [1, 2) 范围内。始终为 1 的整数部分不存储在表示中。位数x定义精度。它是 52 位double。指数以偏移形式存储(必须减去 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 不能以 2^77 的指数以 64 位精度表示 - 尾数中没有 77 位精度。对于 80 位精度也是如此 - 尾数中只有 63 位,在给定指数 2^77 的情况下,比表示 1.0 所需的少 14 位。

就是这样了!这只是科学计算的美妙世界,没有什么能像你在数学课上学到的那样工作......

于 2012-05-08T21:44:46.130 回答