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

问题描述 投票:253回答:14

众所周知,由于舍入和精度问题,比较浮点数是否相等。

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

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

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

python floating-point
14个回答
254
投票

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
投票

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

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


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))

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 <number <= 0.9999999999995e11)

例:

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

0
投票

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

round_to函数使用内置str类中的format method将float向上舍入到一个字符串,该字符串表示所需的小数位数浮点数,然后将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

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 

61
投票

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

return abs(f1 - f2) <= allowed_error

36
投票

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

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

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

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


12
投票

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

来自评论:

值得注意的是,如果你正在做数学繁重的工作并且你并不是绝对需要十进制的精度,这可能会让事情陷入困境。浮动方式,处理速度更快,但不精确。小数非常精确但很慢。


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;这不是与道森完全相同,但它在精神上是相似的。


11
投票

浮点数无法与平等进行比较的常识是不准确的。浮点数与整数没有什么不同:如果你评估“a == b”,如果它们是相同的数字你将得到真,否则你将得到假(理解两个NaN当然不是相同的数字)。

实际的问题是这样的:如果我做了一些计算并且不确定我要比较的两个数字是完全正确的,那又怎么样?浮点和整数的问题是相同的。如果计算整数表达式“7/3 * 3”,它将不会等于“7 * 3/3”。

因此,假设我们问“我如何比较整数的平等性?”在这种情况下。没有一个答案;你应该做什么取决于具体情况,特别是你有什么样的错误以及你想要达到的目标。

以下是一些可能的选择。

如果您希望得到“真实”结果,如果数学上精确的数字相等,那么您可能会尝试使用您执行的计算属性来证明您在两个数字中得到相同的错误。如果这是可行的,并且你比较两个由表达式产生的数字,如果精确计算得到相同的数字,那么你将从比较中得到“真”。另一种方法是您可以分析计算的属性并证明误差永远不会超过一定量,可能是绝对量或相对于其中一个输入或其中一个输出的量。在这种情况下,您可以询问两个计算的数字是否至少相差该数量,如果它们在该时间间隔内,则返回“true”。如果你无法证明错误的界限,你可能会猜测并希望最好。猜测的一种方法是评估许多随机样本,看看你在结果中得到什么样的分布。

当然,因为如果数学上精确的结果是相等的,我们只设置你得到“真”的要求,我们留下了即使它们不相等也能得到“真”的可能性。 (事实上​​,我们可以通过始终返回“true”来满足要求。这使得计算简单但通常是不合需要的,因此我将讨论改善下面的情况。)

如果你想得到一个“假”结果,如果数学上确切的数字是不相等的,你需要证明如果数学上确切的数字不相等,你对数字的评估会产生不同的数字。在许多常见情况下,这可能不可能用于实际目的。那么让我们考虑另一种选择。

一个有用的要求可能是,如果数学上精确的数字相差超过一定数量,我们会得到“假”结果。例如,也许我们将计算在计算机游戏中投掷的球的位置,并且我们想要知道它是否击中了蝙蝠。在这种情况下,如果球击中球棒,我们当然希望得到“真实”,如果球远离击球,我们想要“假”,如果球进入,我们可以接受错误的“真实”答案一个数学上精确的模拟错过了蝙蝠但是在击打蝙蝠的毫米之内。在这种情况下,我们需要证明(或猜测/估计)我们对球的位置和球棒位置的计算具有至多1毫米的组合误差(对于所有感兴趣的位置)。如果球和球棒相距超过一毫米,这将使我们总是返回“假”,如果它们接触则返回“真”,如果它们足够接近可以接受,则返回“真”。

那么,在比较浮点数时,如何决定返回什么,在很大程度上取决于您的具体情况。

至于如何证明计算的误差范围,这可能是一个复杂的主题。在舍入到最接近模式下使用IEEE 754标准的任何浮点实现都会返回最接近任何基本操作(特别是乘法,除法,加法,减法,平方根)的精确结果的浮点数。 (如果是平局,那么低位是偶数。)(特别注意平方根和除法;你的语言实现可能会使用那些不符合IEEE 754的方法。)由于这个要求,我们知道单个结果中的错误最多为最低有效位的1/2。 (如果更多,则舍入将变为不同的数字,该值在值的1/2之内。)

从那里开始变得更加复杂;下一步是执行一个操作,其中一个输入已经有一些错误。对于简单表达式,可以通过计算跟踪这些错误以达到最终错误的界限。实际上,这只能在少数几种情况下完成,例如在高质量的数学图书馆工作。当然,您需要精确控制所执行的操作。高级语言通常会给编译器带来很多麻烦,因此您可能不知道执行的操作顺序。

关于这个话题还有很多可以写的,但是我必须停在那里。总之,答案是:没有用于此比较的库例程,因为没有一个解决方案能够满足大多数值得投入库例程的需求。 (如果与相对或绝对错误间隔进行比较就足够了,您可以在没有库例程的情况下完成。)


5
投票

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

from nose.tools import assert_almost_equals

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

4
投票

math.isclose()一直是added到Python 3.5(source code)。这是Python 2的一个端口。它与Mark Ransom的一线之间的区别在于它可以正确处理“inf”和“-inf”。

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

4
投票

我发现以下比较有帮助:

str(f1) == str(f2)
© www.soinside.com 2019 - 2024. All rights reserved.