在Python中判断两个浮点数的相等
2021-05-11 21:52:18 +08 字数:1025 标签: Python如何判断两个浮点数是否相等? 这是一个所有编程语言都有的小坑。
在其它编程语言,使用==
来判断是绝对的大忌,因为浮点数在低位会丢失精度。
无论float
还是double
都精度有限,只是精度不同。
Python中虽然统一了float
和double
,但不可避免地仍然有这个问题。
精度丢失 ¶
>>> 0.1 + 0.2
0.30000000000000004
简单地说,$0.1$在二进制中,是一个无限循环小数$0.0\dot{0}\dot{0}\dot{1}\dot{1}$。 这虽然违反了习惯十进制的人类直觉,但确实是真实存在。 就像$1 \over 3$在十进制中是无限循环小数$0.\dot{3}$一样,而$1 \over 3$在三进制中,表示为$0.1$。 计算机在处理小数时,是通过二进制寄存器来存储和计算,长度有限,因此会丢失无限循环小数低位的数字。
所以,不同进制在转换过程中,可能存在失真。 表现为,小数的低位不准。 那么浮点数的精度如何?
>>> import sys
>>> sys.float_info.epsilon
2.220446049250313e-16
按照C99的float.h
,精度大概是这个情况。
前面0.1 + 0.2
的那个尾巴是4e-17
,小于2.22e-16
,在精度允许的范围内。
大部分语言的浮点数遵循这个标准。
所以,如果两个浮点数的差,小于这个精度,就可以认为两个数相等。
def eq(a: float, b: float) -> bool:
return abs(a - b) < sys.float_info.epsilon
这也是大部分语言判断浮点数相等的手法。
有时,我们也会容忍epsilon
大一点。
math.isclose ¶
在Python 3.5以后,通常使用math.isclose来判断两个浮点数相等。
>>> import math
>>> math.isclose(0.1 + 0.2, 0.3)
通过查阅文档和测试,发现isclose
函数的实现相当于以下代码:
def isclose(a, b, rel_tol=1e-09, abs_tol=0):
return abs(a - b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol)
相对精度rel_tol
普遍适用的,相当于默认保留9位有效数字。
但有时也会有一些反直觉的现象:
>>> math.isclose(1e10, 1e10 + 1)
True
某些领域,或者计算机日常业务的多数场景,使用abs_tol
会更符合直觉一些。
>>> math.isclose(1e10, 1e10 + 1, rel_tol=0, abs_tol=0.001)
False
>>> math.isclose(1, 1.01, rel_tol=0, abs_tol=0.001)
False
>>> math.isclose(1, 1.001, rel_tol=0, abs_tol=0.001)
True
不要直接利用==
¶
在Python中,==
在判断浮点数的时候,看上去好像实现了math.isclose(a, b, rel_tol=1e-16, abs_tol=0)
:
>>> 1.0000000000001 == 1
False
>>> 1.00000000000001 == 1
True
其实并没有。因为:
>>> 1.0000000000001
1.0000000000001
>>> 1.00000000000001
1.0
1 + 1e-16
只是精度丢失了而已。
Python判断两个浮点数是否相等,底层仍然是C语言实现的,判断的是寄存器的相等性。
MyFloat ¶
但是,通过重载==
,可以实现math.isclose
的功能,看上去简洁一些。
class MyFloat(float):
def __eq__(self, other):
return math.isclose(self, other, rel_tol=0, abs_tol=0.001)
在使用时:
>>> a == MyFloat(0)
>>> a
0.0
>>> a == 0.001
True
>>> 0.001 == a
True
也只是看上去简洁。