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

Gor*_*ley 291 python floating-point

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

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

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

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

Mar*_*som 282

Python 3.5添加了PEP 485中描述的math.isclosecmath.isclose功能.

如果您使用的是早期版本的Python,则文档中会给出等效函数.

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)
Run Code Online (Sandbox Code Playgroud)

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

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

  • 注意当`a`或`b`是'numpy``数组`时,[`numpy.isclose`](http://docs.scipy.org/doc/numpy-dev/reference/generated/numpy.isclose. HTML)工作. (20认同)
  • @marsh`rel_tol`是*相对容差*,它乘以两个参数的幅度中的较大者; 随着值越大,它们之间允许的差异也越大,同时仍然认为它们相等.`abs_tol`是*绝对公差*,在所有情况下都按原样应用.如果差值小于这些公差中的任何一个,则认为这些值相等. (5认同)
  • 不要减少这个答案的价值(我认为这是一个很好的答案),值得注意的是文档还说:"Modulo错误检查等,函数将返回结果......"换句话说,` isclose`函数(上面)不是_complete_实现. (5认同)
  • 为恢复旧线程道歉,但似乎值得指出,'isclose`始终遵循_less_保守标准.我只提到它,因为这种行为对我来说是违反直觉的.如果我指定两个标准,我总是希望较小的容差取代更大. (4认同)
  • @MackieMesser你当然有资格获得你的意见,但这种行为对我来说非常有意义.按照你的定义,没有任何东西可以"接近"零,因为相对容差乘以零总是为零. (3认同)
  • 啊! 这是一个很好的观点,但我完全忽略了这一点。从这个角度来看确实有道理。事实上,这正是我期望的公差被解释为优化等指定的绝对和相对的方式。很好的一点。为了其他人有同样的认识,我将把它保留下来。 (2认同)

And*_*ite 66

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

return abs(f1 - f2) <= allowed_error
Run Code Online (Sandbox Code Playgroud)

  • 根据我的经验,比较浮点数的最佳方法是:`abs(f1-f2)<tol*max(abs(f1),abs(f2))`.这种相对容差是一般比较浮点数的唯一有意义的方法,因为它们通常受小小数位的舍入误差的影响. (8认同)
  • 正如我提供的链接指出的那样,减法仅在您事先知道数字的大致幅度时才有效. (6认同)
  • 只是添加一个简单的示例,为什么它不起作用:`&gt;&gt;&gt; abs(0.04-0.03)&lt;= 0.01`,它会产生`False`。我在darwin上使用Python 2.7.10 [GCC 4.2.1(Apple Inc. build 5666)(dot 3)] (2认同)
  • @schatten是公平的,与特定的比较算法相比,该示例与机器二进制精度/格式有更多关系。当您在系统中输入0.03时,这实际上并不是进入CPU的数字。 (2认同)
  • @AndrewWhite该示例显示`abs(f1- f2)&lt;= allowed_error`不能按预期工作。 (2认同)

小智 38

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

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

numpy.isclose(a, b, rtol=1e-05, atol=1e-08, equal_nan=False)
Run Code Online (Sandbox Code Playgroud)

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

  • @John:很难获得Windows的64位二进制文​​件.很难在Windows上通过`pip`获得numpy. (10认同)
  • @BenBolker如果你必须安装由Python驱动的开放数据科学平台,最好的方法是Anaconda https://www.continuum.io/downloads(pandas,numpy和更多开箱即用) (4认同)
  • “根据您的平台,安装 numpy 可能是一种非常重要的体验。”...嗯什么?哪些平台安装 numpy 是“重要的”?究竟是什么让它变得不平凡? (2认同)
  • 如果两个数组相等,请使用 numpy.isclose(a, b, rtol=1e-05, atol=1e-08, equal_nan=False).all() 获取单个 True/False 值。 (2认同)

jat*_*ism 13

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

来自评论:

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


Eri*_*hil 12

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

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

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

以下是一些可能的选择.

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

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

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

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

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

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

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

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

  • 从上面与Gareth McCaughan的讨论中,正确地与相对误差进行比较基本上相当于"abs(ab)<= eps*max(2** - 1022,abs(a),abs(b))",这不是我的意思d描述简单,当然不是我自己制定的东西.此外,史蒂夫杰西普指出它与max,min,any和all都有类似的复杂性,这些都是内置的.因此,在标准数学模块中提供相对误差比较似乎是一个好主意. (3认同)
  • 你可能忘了键入`from __future__ import division`.如果不这样做,则没有浮点数,并且比较在两个整数之间. (3认同)
  • @xApple:我刚刚在 OS X 10.8.3 上运行 Python 2.7.2 并输入“(7/3*3 == 7*3/3)”。它打印了“False”。 (2认同)
  • 这是一个重要的讨论,但并不是非常有用. (2认同)
  • 看来您只关注计算错误而忽略了表示错误。这些确实存在于浮点数中,但不存在于整数中,这会产生许多浮点数特有的问题。“1”在数据中的表示与文本中的完全相同,而“0.1”则不然。这导致“1 + 2 == 3”为“True”,而“0.1 + 0.2 == 0.3”为“False”。这不是由于计算错误,而是由于表示错误——这是整数所没有的一类错误。 (2认同)

Gar*_*han 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;这不完全是和道森一样,但它在精神上是相似的.

  • 顺便说一句,我同意S. Lott的说法,Right Thing总是取决于你的实际应用,这就是为什么没有一个标准库函数可以满足你所有的浮点比较需求. (2认同)

use*_*509 9

math.isclose()为此添加到 Python 3.5(源代码)。这是它到 Python 2 的一个端口。它与 Mark Ransom 的 one-liner 不同之处在于它可以正确处理“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
Run Code Online (Sandbox Code Playgroud)


小智 8

就绝对误差而言,你可以检查一下

if abs(a - b) <= error:
    print("Almost equal")
Run Code Online (Sandbox Code Playgroud)

关于为什么 float 在 Python 中表现得很奇怪的一些信息: Python 3 教程 03 - if-else、逻辑运算符和初学者最常犯的错误

您还可以使用math.isclose来计算相对误差。


vol*_*myr 5

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

from nose.tools import assert_almost_equals

assert_almost_equals(x, y, places=7) #default is 7
Run Code Online (Sandbox Code Playgroud)


小智 5

这对于您想要确保两个数字“精确度”相同且无需指定容差的情况非常有用:

  • 求两个数的最小精度

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

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)
Run Code Online (Sandbox Code Playgroud)

正如所写,它仅适用于字符串表示中没有“e”的数字(意思是 0.9999999999995e-4 < number <= 0.9999999999995e11)

例子:

>>> isclose(10.0, 10.049)
True
>>> isclose(10.0, 10.05)
False
Run Code Online (Sandbox Code Playgroud)

  • 无限的关闭概念不会对你有好处。`isclose(1.0, 1.1)` 生成 `False`,而 `isclose(0.1, 0.000000000001)` 返回 `True`。 (2认同)