为什么具有精确值的浮点数之和仍然取决于顺序?

Why does the sum of float numbers with exact values still depend on order?

假设我有3个浮点数,可以用浮点数准确表示:

var a=0.1000000000000000055511151231257827021181583404541015625;
var b=0.200000000000000011102230246251565404236316680908203125;
var c=0.299999999999999988897769753748434595763683319091796875;

它们是0.1、0.2、0.3实际存储在float中的值。我认为a+b+c应该等于c+b+a,因为a、b和c都已经四舍五入了,而且是精确值,它们的和应该不依赖于顺序,但现在我测试了它不是:

var a=0.1000000000000000055511151231257827021181583404541015625;
var b=0.200000000000000011102230246251565404236316680908203125;
var c=0.299999999999999988897769753748434595763683319091796875;
console.log(a+b+c==c+b+a);

这是什么原因?

注意:我不是在问 Is floating point math broken?, because 0.1000000000000000055511151231257827021181583404541015625 is already the accurate value in floating point number (some float numbers can be represented without rounding errors, for example 0.5,0.75 and 0.375, see:)。

我的假设是:a、b、c没有舍入误差,所以它们的和也应该没有舍入误差,就像

1+2+3 == 3+2+1
0.5+0.375+0.25 == 0.25+0.375+0.5

但是现在a+b+c不是这种情况,我这里的假设有什么问题吗?

总和不准确

您假设精确值之和是精确的是错误的。

浮点运算使用一些固定格式的数字(例如 float 的 24 个二进制数字)。两个 24 位数字的数学和可能有 25 位数字,因此需要四舍五入以在 24 位数字(和指数)内表示。

此外,当两个具有不同指数的数字相加时,一个数字相对于另一个数字偏移。由于偏移量,总和可能有额外的数字,因此必须再次四舍五入。

当您以不同顺序添加数字时,结果四舍五入可能会有所不同。

不精确和的例子

这些示例使用三位二进制有效数字。

在这个例子中,加法进入一个新的列:

 1.10 • 23
 1.01 • 23
――――――――――
10.11 • 23 Exact sum, too many digits, must be rounded.
11.0  • 23 Sum rounded to three digits.
1.10  • 24 Rounded sum, exponent adjusted to normalize significand.

在此示例中,数字具有不同的指数,对此进行调整会将数字移到新列中:

 1.11 • 23
 1.01 • 25   Different exponent requires adjustment.

 0.0111 • 25 Adjusted to match exponent.
 1.01   • 25
――――――――――――
 1.1011 • 25 Exact sum, too many digits, must be rounded.
 1.11   • 25 Rounded sum.

非关联和的例子

现在我们可以看看以不同的方式将三个数字相加,并看到产生不同的总和。

我们将比较(1.10•20 + 1.10•20) + 1.00•24) 到 1.10•20 + (1.10•20 + 1.00•24).

对于第一个表达式,我们添加第一个和第二个操作数,然后是第三个:

Add first and second operands:
 1.10 • 20 First operand.
 1.10 • 20 Second operand.
――――――――――
11.00 • 20 Exact sum, too many digits, must be rounded.
11.0  • 20 Rounded sum, must be normalized.
1.10  • 21 Normalized, rounded sum.

Add previous result and third operand:
 1.10 • 21 Previous result.
 1.00 • 24 Third operand.

Exponents do not match, so adjust and then add:
 0.00110 • 24 Previous result adjusted to match exponent.
 1.00    • 24 Third operand.
――――――――――――
 1.00110 • 24 Exact sum, too many digits, must be rounded.
 1.01    • 24 Rounded sum.

对于第二个表达式,我们添加第二个和第三个操作数,然后是第一个:

Add second and third:
 1.10    • 20 Second operand.
 1.00    • 24 Third operand.

Exponents do not match, so adjust, then add:
 0.000110 • 24 Second operand adjusted to match exponent.
 1.00     • 24 Third operand.
――――――――――――――
 1.000110 • 24 Exact sum, too many digits, must be rounded.
 1.00     • 24 Rounded sum.

Add first operand and previous result:
 1.10     • 20 First operand.
 1.00     • 24 Previous result.

Exponents do not match, so adjust and then add:
 0.000110 • 24 First operand adjusted to match exponent.
 1.00     • 24 Previous result.
―――――――――――――
 1.000110 • 24 Exact sum, too many digits, must be rounded.
 1.00     • 24 Rounded sum.

第一个表达式产生 1.01•24,而第二个表达式产生 1.00•24。所以操作数相加的顺序会影响结果。

我的假设是加法的中间值可能有不同的舍入点误差。例如,a + b 可能会产生舍入误差。并且 b + c 可能不会,或者结果值在与最终值相加时可能会导致不同类型的浮点错误。加括号让运算顺序和中间值更清晰:

(a + b) + c

(c + b) + a