双精度的乘法不如浮点数的乘法精确

Double's multiplication is less precise than float's one

本文关键字:浮点数 双精度      更新时间:2023-10-16

假设有方程y = k1 * x + b1 = k2 * x + b2。我们用浮点数来计算x。我知道这是一个糟糕的选择,但我想知道我得到结果的原因。同样,我们用这个x计算y然后做同样的事情,但是用double(x)考虑以下代码:

std::cout.precision(20);
float k1, b1, k2, b2;
std::cin >> k1 >> b1 >> k2 >> b2;
float x_f = (b2 - b1) / (k1 - k2);
double x_d = x_f;
printFloat(x_f); // my function which prints number and it's binary representation
printDouble(x_d);
float y_f = x_f * k1 + b1;
double y_d = x_d * k1 + b1;
printFloat(y_f);
printDouble(y_d);

并以k1 = -4653, b1 = 9968, k2 = 520, b2 = -1370惊奇地得到以下结果:

x_f = 2.19176483154296875 01000000000011000100010111100000
x_d = 2.19176483154296875 0100000000000001100010001011110000000000000000000000000000000000
y_f = -230.2822265625 11000011011001100100100001000000
y_d = -230.28176116943359375 1100000001101100110010010000010000110000000000000000000000000000

而更精确的答案(用Python Decimal计算)是:

x = 2.191764933307558476705973323023390682389
y = -230.28223468006959211289387202783684516

并且float的答案比double的答案更接近!为什么会发生这种情况?我用gdb(在64位Ubuntu 14.04 g++ 4.8.4上编译)进行了调试,并查看了说明,它们都没问题,所以这是由于乘法。

这是一个巧合,四舍五入抵消,最终更接近float而不是double。差异的根源在于x_d * k1被提升为double,而x_f * k1被评估为float

为了提供一个更简单的例子,说明这种舍入如何使低精度类型产生更准确的答案,考虑两个新的数字类型sf2sf3,它们分别存储以10为基数的数字,分别有2和3位有效数字。然后考虑以下计算:

// Calculate (5 / 4) * 8. Expected result: 10
sf2 x_2 = 5.0 / 4.0; // 1.3
sf2 y_2 = x_2 * 8.0; // 10
sf3 x_3 = x_2; // 1.30
sf3 y_3 = x_3 * 8.0; // 10.4

请注意,使用上述类型,尽管所有sf2值都可以用sf3类型表示,但sf2的计算更准确。这是因为计算x_2时将1.25四舍五入到10时将CC_12四舍五入到1.3。但是,当使用sf3类型完成第二次计算时,保留初始的四舍五入,但不再进行四舍五入。

这是您在处理浮点类型时会遇到的许多陷阱的一个例子。