我知道大多数小数都没有确切的浮点表示形式(Is floating point math broken?)。

但是我不明白为什么4*0.1可以很好地打印为0.4,但是3*0.1却不能,
这两个值实际上都有丑陋的十进制表示形式:

>>> 3*0.1
0.30000000000000004
>>> 4*0.1
0.4
>>> from decimal import Decimal
>>> Decimal(3*0.1)
Decimal('0.3000000000000000444089209850062616169452667236328125')
>>> Decimal(4*0.1)
Decimal('0.40000000000000002220446049250313080847263336181640625')

最佳答案

简单的答案是因为3*0.1 != 0.3是由于量化(舍入)误差引起的(而4*0.1 == 0.4是因为乘以2的幂通常是“精确”运算)。 Python试图找到最短的字符串并将其四舍五入为所需的值,因此它可以将4*0.1显示为0.4,因为它们相等,但是由于它们不相等,因此无法将3*0.1显示为0.3
您可以在Python中使用.hex方法查看数字的内部表示形式(基本上是确切的二进制浮点值,而不是以10为底的近似值)。这可以帮助解释幕后情况。

>>> (0.1).hex()
'0x1.999999999999ap-4'
>>> (0.3).hex()
'0x1.3333333333333p-2'
>>> (0.1*3).hex()
'0x1.3333333333334p-2'
>>> (0.4).hex()
'0x1.999999999999ap-2'
>>> (0.1*4).hex()
'0x1.999999999999ap-2'
0.1是0x1.999999999999a乘以2 ^ -4。末尾的“a”表示数字10-换句话说,二进制浮点数中的0.1略大于“精确”值0.1(因为最终的0x0.99舍入为0x0.a)。当您将其乘以4(2的幂)时,指数会上移(从2 ^ -4到2 ^ -2),但数字不变,因此为4*0.1 == 0.4
但是,当乘以3时,0x0.99和0x0.a0(0x0.07)之间的微小差异会放大为0x0.15错误,在最后一个位置显示为一位错误。这导致0.1 * 3略大于0.3的舍入值。
Python 3的float repr设计为可双向访问的,也就是说,显示的值应可精确转换为原始值(所有floats float(repr(f)) == ff)。因此,它不能以完全相同的方式显示0.30.1*3,否则两个不同的数字在往返后将以相同的方式结束。因此,Python 3的repr引擎选择显示一个带有明显错误的代码。

10-08 13:39