首页 > 解决方案 > 对浮点相等性测试感到困惑?

问题描述

数字 6.35 无法准确表示:

alert( 6.35.toFixed(20) ); // 6.34999999999999964473

但为什么6.35 * 10 == 63.5是真的?

6.35 不准确,10 准确,63.5 准确。我不明白(不准确*准确)如何等于准确。

标签: javascriptfloating-pointprecision

解决方案


这是一个逻辑错误:最后的运算 6.35 * 10.0 是不准确的而不是准确的。
只是可能会发生几个连续的“舍入误差”消失,因为它们也可能会累积。

最接近 635/100 的双倍是635/100 - 1/2,814,749,767,106,560
或者,如果您愿意:635/100 - 1/(10 * 2^48)
所以准确的*10操作应该回答635/10 - 1/(2^48)
但是这个数量不能表示为双精度(见下文)......
所以最后一个操作是不准确的。

这两个邻居是 63.5(正是635/10)和它的前身635/10 - 1/(2^47)
一个有趣的精确平局案例:精确数量与两个可表示的双邻域的距离相同,默认舍入模式是四舍五入到最近,平到偶数,因此 FPU 将选择具有偶数有效位的双精度数,即635/10

这是运气还是 IEEE 754 算法的一个很好的属性?
如果我在 Squeak/Pharo Smalltalk 中评估这个片段(它具有精确的分数和精确算术值的比较):

(1 to: 10000) count: [:x | (x/10.0) = (x/10) and: [(x/100.0) ~= (x/100)]].

我得到了 1600 个案例,其中 x/10 完全可以表示为 double,而 x/100 则不是。

如果我选择这 1600 个案例,并验证舍入误差是否消失:

((1 to: 10000) select: [:x | (x/10.0) = (x/10) and: [(x/100.0) ~= (x/100)]])
    count: [:x | (x/100.0*10) = (x/10)]

我计算了 1600 个错误消除的案例中的 1600 个,因此它是 IEEE754 算法的一个很好的属性。但这仍然是运气。

如果我通过除以 1000.0 然后乘以 100 重试,我会得到这个问题的错误答案:

((1 to: 10000) select: [:x | (x/10.0) = (x/10) and: [(x/1000.0) ~= (x/1000)]])
    allSatisfy: [:x | (x/1000.0*100) = (x/10)]

1920 年中有 1649 例的答案是正确的,这已经是一个不错的分数了。


推荐阅读