In [1]:
0.1 == 0.10000000000000000000001
Out[1]:
In [2]:
0.1+0.1+0.1 == 0.3
Out[2]:
这些看起来违反常识的“错误”并非 Python 的错,而是由浮点数的规则所决定的,即使放到其它语言中结果也是这样的。要理解计算机中浮点数的表示规则,先来看现实世界中十进制小数是如何表示的:
1.234 = 1 + 1/10 + 2/100 + 3/1000
可以用下面的公式来表示:
$$d = \sum_{i=-n}^m10^i*d_i$$其中 $d_i$ 是十进制中 0~9 的数字。而如果是一个二进制的小数:
1.001 = 1 + 0/2 + 0/4 + 1/8
可以用下面的公式来表示:
$$d = \sum_{i=-n}^m2^i*d_i$$其中 $d_i$ 是二进制中的 0 或 1。Python 中的浮点数都是双精度的,也就说采用 64 位来表示一个小数,那这 64 位分别有多少用来表示整数部分和小数部分呢?根据 IEEE 标准,考虑到符号位,双精度表示法是这样分配的:
$$d = s * \sum_{i=-52}^{11} 2^i*d_i$$也就是说用1位表示符号位,11位表示整数部分,52位表示小数部分。正如十进制中我们无法精确表示某些分数(如10/3
),浮点数中通过 d1/2 + d2/4 + ...
的方式也会出现这种情况,比如上面的例子中,十进制中简单的 0.1
就无法在二进制中精确描述,而只能通过近似表示法表示出来:
In [3]:
(0.1).as_integer_ratio()
Out[3]:
也就是说 0.1
是通过 3602879701896397/36028797018963968
来近似表示的,很明显这样近似的表示会导致许多差距很小的数字公用相同的近似表示数,例如:
In [4]:
(0.10000000000000001).as_integer_ratio()
Out[4]:
在 Python 中所有这些可以用相同的近似数表示的数字统一采用最短有效数字来表示:
In [5]:
print(0.10000000000000001)
In [6]:
a = .1 + .1 + .1
b = .3
print(a.as_integer_ratio())
print(b.as_integer_ratio())
print(a == b)
为了解决运算中的问题,IEEE 标准还指定了一个舍入规则(round),即 Python 中内置的 round
方法,我们可以通过舍入的方式取得两个数的近似值,来判断其近似值是否相等:
In [7]:
round(a, 10) == round(b, 10)
Out[7]:
当然这种舍入的方式并不一定是可靠的,依赖于舍入的选择的位数,位数太大,就失去了 round
的作用,太小,就会引入别的错误:
In [8]:
print(round(a, 17) == round(b, 17))
print(round(0.1, 1) == round(0.111, 1))
Python 中使用更精确的浮点数可以通过 decimal
和 fractions
两个模块,从名字上也能猜到,decimal
表示完整的小数,而 fractions
通过分数的形式表示小数:
In [9]:
from decimal import Decimal
a = Decimal(0.1)
b = Decimal(0.1000000000000001)
c = Decimal(0.10000000000000001)
print(a)
print(b)
print(c)
a == b == c
Out[9]:
In [10]:
from fractions import Fraction
f1 = Fraction(1, 10) # 0.1
print(float(f1))
f3 = Fraction(3, 10) # 0.3
print(float(f3))
print(f1 + f1 + f1 == f3)
浮点数这些奇特的特性让我们不得不在使用的时候格外注意,尤其是当有一定的精度要求的情况下。如果真的是对精度要求较高且需要频繁使用浮点数,建议使用更专业的 SciPy 科学计算包。