为什么 `sum([0.1] * 12) == 1.2` 是 True 而 `math.fsum([0.1] * 12) == 1.2` 是 False?

Why `sum([0.1] * 12) == 1.2` is True meanwhile `math.fsum([0.1] * 12) == 1.2` is False?

学习python内置float函数的时候,看了the floating point doc。也有了一些了解。

但是做了一堆实验,还是遇到了一些悬而未决的疑惑

疑问1

在第一段提到的教程文档中,它给了我们一个例子:

>>> sum([0.1] * 10) == 1.0
False
>>> math.fsum([0.1] * 10) == 1.0
True

根据文档的说明,我得到的印象是 math.fsum 在进行浮点求和时会为我们提供更准确的结果。

但我在 range(20) 中发现了一个特例,其中 sum([0.1] * 12) == 1.2 求值为 True,同时 math.fsum([0.1] * 12) == 1.2 求值为 False。这让我很困惑。

为什么会这样?
还有sum做浮点求和的机制是什么?

疑问2

我发现对于一些浮点运算,加运算与其等效的乘运算具有相同的效果。比如0.1+0.1+0.1+0.1+0.1等于0.1*5。但在某些情况下,并不等价,比如 0.1 12 次相加不等于 0.1*12。这让我真的很困惑。根据 float 是根据 IEEE-754 标准计算的固定值。根据数学原理,这种加法应该等于它的等价乘法。唯一的解释是 python 没有完全应用数学原理,发生了一些棘手的事情。

但是这个棘手的东西的机制和细节是什么?

In [64]: z = 0
In [64]: z = 0

In [65]: 0.1*12 == 1.2
Out[65]: False

In [66]: for i in range(12):
    ...:     z += 0.1
    ...:

In [67]: z == 1.2
Out[67]: True


In [71]: 0.1*5 == 0.5
Out[71]: True

In [72]: z = 0

In [73]: for i in range(5):
    ...:     z += 0.1
    ...:

In [74]: z == 0.5
Out[74]: True

当.1转换为64位二进制IEEE-754浮点数时,结果正好是0.1000000000000000055511151231257827021181583404541015625。当你把这个单独相加12次时,相加时出现各种舍入误差,最后的和正好是1.1999999999999999555910790149937383830547332763671875.

巧合的是,1.2转为浮点数,结果也正好是1.1999999999999999555910790149937383830547332763671875。这是一个巧合,因为在添加 .1 时有些舍入误差向上舍入,有些舍入误差向下舍入,最终结果为 1.1999999999999999555910790149937383830547332763671875。

但是,如果将 .1 转换为浮点数,然后使用精确数学将其相加 12 次,则结果正好是 1.20000000000000006661338147750939242541790008544921875。 Python 的 math.fsum 可能会在内部产生这个值,但它不适合 64 位二进制浮点数,所以它被四舍五入为 1.20000000000000017763568394002504646778106689453125.

您可以看到,更准确的值1.2000000000000177635635683940025046467778106689453125与将1.2直接转换为浮动点的结果不同,是1.199999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999000000000000英尺。

中,我通过添加 .1 来详细检查舍入误差。