为什么 4*0.1 的浮点值在 Python 3 中看起来不错,但 3*0.1 不好看?
问题描述
我知道大多数小数都没有精确的浮点表示(浮点数学有问题吗?).p>
但我不明白为什么 4*0.1
可以很好地打印为 0.4
,但 3*0.1
不是,当这两个值实际上都有丑陋的十进制表示:
>>>3*0.10.30000000000000004>>>4*0.10.4>>>从十进制导入十进制>>>十进制(3*0.1)十进制('0.30000000000000000444089209850062616169452667236328125')>>>十进制(4*0.1)十进制('0.400000000000000002220446049250313080847263336181640625')
解决方案 简单的答案是因为 3*0.1 != 0.3
由于量化(舍入)误差(而 4*0.1== 0.4
因为乘以 2 的幂通常是精确"运算).Python 试图找到可以四舍五入到所需值的最短字符串,因此它可以将 4*0.1
显示为 0.4
,因为它们是相等的,但它不能将 3*0.1
显示为 0.3
因为它们不相等.
您可以使用 Python 中的 .hex
方法查看数字的内部表示(基本上是 exact 二进制浮点值,而不是 base-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.一个"最后表示数字 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 的浮点 repr
被设计为 round-trippable,也就是说,显示的值应该完全可以转换为原始值(float(repr(f)) == f
对于所有浮点数 f
).因此,它不能以完全相同的方式显示0.3
和0.1*3
,否则两个不同的数字会在往返后最终相同.因此,Python 3 的 repr
引擎选择显示一个带有轻微明显错误的文件.
I know that most decimals don't have an exact floating point representation (Is floating point math broken?).
But I don't see why 4*0.1
is printed nicely as 0.4
, but 3*0.1
isn't, when
both values actually have ugly decimal representations:
>>> 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')
解决方案
The simple answer is because 3*0.1 != 0.3
due to quantization (roundoff) error (whereas 4*0.1 == 0.4
because multiplying by a power of two is usually an "exact" operation). Python tries to find the shortest string that would round to the desired value, so it can display 4*0.1
as 0.4
as these are equal, but it cannot display 3*0.1
as 0.3
because these are not equal.
You can use the .hex
method in Python to view the internal representation of a number (basically, the exact binary floating point value, rather than the base-10 approximation). This can help to explain what's going on under the hood.
>>> (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 is 0x1.999999999999a times 2^-4. The "a" at the end means the digit 10 - in other words, 0.1 in binary floating point is very slightly larger than the "exact" value of 0.1 (because the final 0x0.99 is rounded up to 0x0.a). When you multiply this by 4, a power of two, the exponent shifts up (from 2^-4 to 2^-2) but the number is otherwise unchanged, so 4*0.1 == 0.4
.
However, when you multiply by 3, the tiny little difference between 0x0.99 and 0x0.a0 (0x0.07) magnifies into a 0x0.15 error, which shows up as a one-digit error in the last position. This causes 0.1*3 to be very slightly larger than the rounded value of 0.3.
Python 3's float repr
is designed to be round-trippable, that is, the value shown should be exactly convertible into the original value (float(repr(f)) == f
for all floats f
). Therefore, it cannot display 0.3
and 0.1*3
exactly the same way, or the two different numbers would end up the same after round-tripping. Consequently, Python 3's repr
engine chooses to display one with a slight apparent error.
相关文章