为什么 `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。也有了一些了解。
- Float的真实价值与他们的示范价值不同,比如
0.1
的真实价值是'0.1000000000000000055511151231257827021181583404541015625'
- python 中的任何浮点数都有一个使用 IEEE-754 的固定值
- math.fsum 为我们提供与输入的精确数学和最接近的可精确表示的值
但是做了一堆实验,还是遇到了一些悬而未决的疑惑
疑问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 来详细检查舍入误差。
学习python内置float函数的时候,看了the floating point doc。也有了一些了解。
- Float的真实价值与他们的示范价值不同,比如
0.1
的真实价值是'0.1000000000000000055511151231257827021181583404541015625'
- python 中的任何浮点数都有一个使用 IEEE-754 的固定值
- math.fsum 为我们提供与输入的精确数学和最接近的可精确表示的值
但是做了一堆实验,还是遇到了一些悬而未决的疑惑
疑问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英尺。
在