提问人:Aivar 提问时间:9/21/2016 最后编辑:CommunityAivar 更新时间:9/7/2020 访问量:17347
为什么 4*0.1 的浮点值在 Python 3 中看起来不错,但 3*0.1 却不然?
Why does the floating-point value of 4*0.1 look nice in Python 3 but 3*0.1 doesn't?
问:
我知道大多数小数没有精确的浮点表示(浮点数学坏了吗?
但我不明白为什么打印得很好,但不是,当
这两个值实际上都有丑陋的十进制表示: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')
答:
repr
(在 Python 3 中)将根据需要输出尽可能多的数字,以使值明确。在这种情况下,乘法的结果不是最接近 0.3 的值(十六进制0x1.33333333333333p-2),它实际上高出一个 LSB(0x1.333333333333334p-2),因此它需要更多的数字才能将其与 0.3 区分开来。str
3*0.1
另一方面,乘法确实得到最接近 0.4 的值(十六进制0x1.99999999999ap-2),因此它不需要任何额外的数字。4*0.1
你可以很容易地验证这一点:
>>> 3*0.1 == 0.3
False
>>> 4*0.1 == 0.4
True
我在上面使用了十六进制表示法,因为它既漂亮又紧凑,并显示了两个值之间的位差。您可以使用例如 自行执行此操作。如果你更愿意看到他们所有的十进制荣耀,这里是:(3*0.1).hex()
>>> Decimal(3*0.1)
Decimal('0.3000000000000000444089209850062616169452667236328125')
>>> Decimal(0.3)
Decimal('0.299999999999999988897769753748434595763683319091796875')
>>> Decimal(4*0.1)
Decimal('0.40000000000000002220446049250313080847263336181640625')
>>> Decimal(0.4)
Decimal('0.40000000000000002220446049250313080847263336181640625')
评论
简单的答案是因为由于量化(舍入)误差(而因为乘以 2 的幂通常是“精确”的运算)。Python 试图找到四舍五入到所需值的最短字符串,因此它可以显示为,因为它们相等,但不能显示为,因为它们不相等。3*0.1 != 0.3
4*0.1 == 0.4
4*0.1
0.4
3*0.1
0.3
您可以使用 Python 中的方法查看数字的内部表示形式(基本上是精确的二进制浮点值,而不是以 10 为基数的近似值)。这有助于解释引擎盖下发生的事情。.hex
>>> (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 的浮点数被设计为可往返的,也就是说,显示的值应该完全可以转换为原始值(对于所有浮点数)。因此,它不能以完全相同的方式显示,否则两个不同的数字在往返后最终会相同。因此,Python 3 的引擎选择显示一个有轻微明显错误的引擎。repr
float(repr(f)) == f
f
0.3
0.1*3
repr
评论
.hex()
float.fromhex()
e
p
p
float.hex
float.fromhex
这是从其他答案中得出的简化结论。
如果您在 Python 的命令行上检查浮点数或打印它,它会通过创建其字符串表示的函数。
repr
从 3.2 版本开始,Python 的 和 使用复杂的舍入方案,它更喜欢 如果可能的话,漂亮的小数,但在 保证浮点数之间的双射(一对一)映射所必需的 及其字符串表示形式。
str
repr
该方案保证了 对于简单来说,看起来不错的值 小数,即使它们不能 精确地表示为浮点数(例如,当 .
repr(float(s))
s = "0.1")
同时,它保证了每个浮动都保持
float(repr(x)) == x
x
评论
str
repr
repr
str
repr
str
repr
str
repr
并非真正特定于 Python 的实现,但应该适用于任何浮点数到十进制字符串函数。
浮点数本质上是一个二进制数,但在科学记数法中具有固定的有效数字限制。
任何具有不与基数共享的质数因子的数的倒数将始终导致重复的点表示。例如,1/7 有一个质因数 7,它不与 10 共享,因此具有重复的十进制表示,而质因数为 2 和 5 的 1/10 也是如此,后者不与 2 共享;这意味着 0.1 不能用点点之后的有限位数来精确表示。
由于 0.1 没有精确的表示,因此将近似值转换为小数点字符串的函数通常会尝试近似某些值,以便它们不会得到像 0.10000000000004121 这样的不直观结果。
由于浮点数是科学记数法,因此任何乘以基数的幂只会影响数字的指数部分。例如,1.231e+2 * 100 = 1.231e+4 表示十进制表示法,同样,1.00101010e11 * 100 = 1.00101010e101 表示二进制表示法。如果我乘以基数的非幂,有效数字也会受到影响。例如,1.2e1 * 3 = 3.6e1
根据所使用的算法,它可能会尝试仅根据有效数字来猜测常见的小数。0.1 和 0.4 在二进制中都具有相同的有效数字,因为它们的浮点数基本上分别是 (8/5)(2^-4) 和 (8/5)(2^-6) 的截断。如果算法将 8/5 sigfig 模式识别为十进制 1.6,那么它将在 0.1、0.2、0.4、0.8 等上工作。它也可能具有其他组合的魔术 sigfig 模式,例如浮点数 3 除以浮点数 10 和其他在统计上可能由除以 10 形成的魔术模式。
在 3*0.1 的情况下,最后几个有效数字可能与将浮点数 3 除以浮点数 10 不同,导致算法无法识别 0.3 常数的幻数,具体取决于其对精度损失的容差。
编辑:https://docs.python.org/3.1/tutorial/floatingpoint.html
有趣的是,有许多不同的十进制数共享相同的最接近的近似二进制分数。例如,数字 0.1 和 0.100000000000000001 和 0.10000000000000000055511151231257827021181583404541015625 都近似于 3602879701896397 / 2 ** 55。由于所有这些十进制值都具有相同的近似值,因此可以显示其中任何一个值,同时仍保留不变的 eval(repr(x)) == x。
精度损失没有容差,如果浮点数 x (0.3) 不完全等于浮点数 y (0.1*3),则 repr(x) 不完全等于 repr(y)。
评论
上一个:Python 中的负零
评论
0.3000000000000000444089209850062616169452667236328125
0.30000000000000004
0.40000000000000002220446049250313080847263336181640625
.4