Double 的乘法不如 float 的精确
Double's multiplication is less precise than float's one
假设我们有方程 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 十进制计算)是:
x = 2.191764933307558476705973323023390682389
y = -230.28223468006959211289387202783684516
而且浮点数的答案比双数更接近!为什么会这样?用gdb调试过(64位Ubuntu 14.04 g++ 4.8.4编译)查看说明,都ok,原来是乘法的缘故。
巧合的是舍入抵消了 float
比 double
更接近。差异的来源是 x_d * k1
被提升为 double
而 x_f * k1
被评估为 float
.
为了提供一个更简单的例子来说明这种舍入如何导致精度较低的类型产生更准确的答案,请考虑两种新的数字类型 sf2
和 sf3
,每个它存储以 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
到 1.3
的舍入在 10.4
到 10
时正好被取消。但是当使用 sf3
类型完成第二次计算时,会保留初始舍入但不再发生舍入。
这是您在处理浮点类型时会遇到的许多陷阱的一个例子。
假设我们有方程 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 十进制计算)是:
x = 2.191764933307558476705973323023390682389
y = -230.28223468006959211289387202783684516
而且浮点数的答案比双数更接近!为什么会这样?用gdb调试过(64位Ubuntu 14.04 g++ 4.8.4编译)查看说明,都ok,原来是乘法的缘故。
巧合的是舍入抵消了 float
比 double
更接近。差异的来源是 x_d * k1
被提升为 double
而 x_f * k1
被评估为 float
.
为了提供一个更简单的例子来说明这种舍入如何导致精度较低的类型产生更准确的答案,请考虑两种新的数字类型 sf2
和 sf3
,每个它存储以 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
到 1.3
的舍入在 10.4
到 10
时正好被取消。但是当使用 sf3
类型完成第二次计算时,会保留初始舍入但不再发生舍入。
这是您在处理浮点类型时会遇到的许多陷阱的一个例子。