6

在Python中判断两个浮点数的相等

 2 years ago
source link: https://note.qidong.name/2021/05/python-float-tolerance/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

在Python中判断两个浮点数的相等

2021-05-11 21:52:18 +08  字数:1025  标签: Python

如何判断两个浮点数是否相等? 这是一个所有编程语言都有的小坑。

在其它编程语言,使用==来判断是绝对的大忌,因为浮点数在低位会丢失精度。 无论float还是double都精度有限,只是精度不同。 Python中虽然统一了floatdouble,但不可避免地仍然有这个问题。

精度丢失

>>> 0.1 + 0.2
0.30000000000000004

简单地说,0.1在二进制中,是一个无限循环小数0.00˙0˙1˙1˙。 这虽然违反了习惯十进制的人类直觉,但确实是真实存在。 就像13在十进制中是无限循环小数0.3˙一样,而13在三进制中,表示为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

也只是看上去简洁。

参考


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK