在Python中比较浮点数几乎相等的最佳方法是什么?

时间:2011-04-08 13:02:41

标签: python floating-point

众所周知,由于四舍五入和精度问题,比较浮点数是否平等有点过分。

例如: https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/

在Python中处理这个问题的推荐方法是什么?

当然,这个地方有一个标准的库函数吗?

16 个答案:

答案 0 :(得分:245)

Python 3.5添加了math.isclose and cmath.isclose functions中描述的PEP 485

如果您使用的是早期版本的Python,则documentation中会提供等效函数。

def isclose(a, b, rel_tol=1e-09, abs_tol=0.0):
    return abs(a-b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol)

rel_tol是相对容差,它乘以两个参数的大小越大;随着值越大,它们之间允许的差异也越大,同时仍然认为它们相等。

abs_tol是绝对容差,在所有情况下都按原样应用。如果差异小于这些公差中的任何一个,则认为这些值相等。

答案 1 :(得分:57)

以下简单的事情不够好吗?

return abs(f1 - f2) <= allowed_error

答案 2 :(得分:36)

我同意Gareth的答案可能最适合作为轻量级的功能/解决方案。

但是我认为如果您使用的是NumPy或者正在考虑它会有所帮助,那么就有一个打包的功能。

numpy.isclose(a, b, rtol=1e-05, atol=1e-08, equal_nan=False)

虽然有点免责声明:根据您的平台安装NumPy可能是非常重要的体验。

答案 3 :(得分:12)

使用Python的decimal模块,该模块提供Decimal类。

来自评论:

  

值得注意的是,如果你是   做数学繁重的工作而你却没有   绝对需要精确度   小数,这真的可以搞砸了   下。花车方式,方式更快   处理,但不精确。小数是   非常精确但很慢。

答案 4 :(得分:11)

我不知道实现Dawson的AlmostEqual2sComplement函数的Python标准库(或其他地方)中的任何内容。如果这是你想要的那种行为,你必须自己实现它。 (在这种情况下,你可能会更好地使用道格if abs(a-b) <= eps1*(abs(a)+abs(b)) + eps2或类似形式的传统测试,而不是使用道森聪明的按位攻击。要获得类似道森的行为,你可能会说if abs(a-b) <= eps*max(EPS,abs(a),abs(b))对于一些小的固定EPS;这与道森不完全相同,但它在精神上是相似的。

答案 5 :(得分:9)

答案 6 :(得分:5)

如果你想在测试/ TDD环境中使用它,我会说这是一种标准方式:

from nose.tools import assert_almost_equals

assert_almost_equals(x, y, places=7) #default is 7

答案 7 :(得分:4)

我发现以下比较有帮助:

str(f1) == str(f2)

答案 8 :(得分:4)

math.isclose()已经added为Python 3.5(source code)。这是Python 2的一个端口。它与Mark Ransom的单行程的区别在于它可以处理&#34; inf&#34;和&#34; -inf&#34;正常。

def isclose(a, b, rel_tol=1e-09, abs_tol=0.0):
    '''
    Python 2 implementation of Python 3.5 math.isclose()
    https://hg.python.org/cpython/file/tip/Modules/mathmodule.c#l1993
    '''
    # sanity check on the inputs
    if rel_tol < 0 or abs_tol < 0:
        raise ValueError("tolerances must be non-negative")

    # short circuit exact equality -- needed to catch two infinities of
    # the same sign. And perhaps speeds things up a bit sometimes.
    if a == b:
        return True

    # This catches the case of two infinities of opposite sign, or
    # one infinity and one finite number. Two infinities of opposite
    # sign would otherwise have an infinite relative tolerance.
    # Two infinities of the same sign are caught by the equality check
    # above.
    if math.isinf(a) or math.isinf(b):
        return False

    # now do the regular computation
    # this is essentially the "weak" test from the Boost library
    diff = math.fabs(b - a)
    result = (((diff <= math.fabs(rel_tol * b)) or
               (diff <= math.fabs(rel_tol * a))) or
              (diff <= abs_tol))
    return result

答案 9 :(得分:1)

对于某些可以影响源编号表示的情况,可以使用整数分子和分母将它们表示为分数而不是浮点数。这样你就可以进行精确的比较。

有关详细信息,请参阅分数模块中的Fraction

答案 10 :(得分:1)

我喜欢@Sesquipedal的建议但是经过修改(两个值为0时的特殊用例返回False)。就我而言,我使用的是Python 2.7,并且只使用了一个简单的函数:

if f1 ==0 and f2 == 0:
    return True
else:
    return abs(f1-f2) < tol*max(abs(f1),abs(f2))

答案 11 :(得分:1)

在需要确保2个数字相同且“不超过精度”的情况下很有用,无需指定公差:

  • 查找2个数字的最小精度

  • 将它们都舍入到最低精度并进行比较

def isclose(a,b):                                       
    astr=str(a)                                         
    aprec=len(astr.split('.')[1]) if '.' in astr else 0 
    bstr=str(b)                                         
    bprec=len(bstr.split('.')[1]) if '.' in bstr else 0 
    prec=min(aprec,bprec)                                      
    return round(a,prec)==round(b,prec)                               

按照书面规定,仅适用于字符串表示形式中不含'e'的数字(表示0.9999999999995e-4 <数字<= 0.9999999999995e11)

示例:

>>> isclose(10.0,10.049)
True
>>> isclose(10.0,10.05)
False

答案 12 :(得分:0)

这可能是一个有点丑陋的黑客,但是当你不需要超过默认的浮点精度(大约11位小数)时,它的效果非常好。在python 2.7上运行良好。

round_to 函数使用内置str类中的format method将浮点数向上舍入为表示浮点数的字符串需要小数,然后将eval内置函数应用于圆形浮点字符串以返回浮点数字类型。

is_close 函数只是将一个简单的条件应用于向上舍入的浮点数。

def round_to(float_num, decimal_precision):
    return eval("'{:." + str(int(decimal_precision)) + "f}'.format(" + str(float_num) + ")")

def is_close(float_a, float_b, decimal_precision):
    if round_to(float_a, decimal_precision) == round_to(float_b, decimal_precision):
        return True
    return False

a = 10.0 / 3
# Result: 3.3333333333333335
b = 10.0001 / 3
# Result: 3.3333666666666666

print is_close(a, b, decimal_precision=4)
# Result: False

print is_close(a, b, decimal_precision=3)
# Result: True

答案 13 :(得分:0)

要比较不带atol/rtol的给定小数:

def almost_equal(a, b, decimal=6):
    return '{0:.{1}f}'.format(a, decimal) == '{0:.{1}f}'.format(b, decimal)

print(almost_equal(0.0, 0.0001, decimal=5)) # False
print(almost_equal(0.0, 0.0001, decimal=4)) # True 

答案 14 :(得分:0)

就绝对错误而言,您只需检查

GraphQL

一些信息说明为什么float在Python中表现怪异 https://youtu.be/v4HhvoNLILk?t=1129

您也可以将math.isclose用于相对错误

答案 15 :(得分:-1)

使用 == 是一个简单的好方法,如果您不关心准确度。

# Python 3.8.5
>>> 1.0000000000001 == 1
False
>>> 1.00000000000001 == 1
True

但要注意0

>>> 0 == 0.00000000000000000000000000000000000000000001
False

0 始终为零。


如果您想控制容差,请使用 math.isclose

默认 a == b 等价于 math.isclose(a, b, rel_tol=1e-16, abs_tol=0)


如果您仍想使用具有自定义容差的 ==

>>> class MyFloat(float):
        def __eq__(self, another):
        return math.isclose(self, another, rel_tol=0, abs_tol=0.001)

>>> a == MyFloat(0)
>>> a
0.0
>>> a == 0.001
True

到目前为止,我没有找到任何地方可以为 float 全局配置它。此外,mock 也不适用于 float.__eq__