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


331

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

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

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

当然在某个地方有一个标准的库函数吗?


@tolomea:由于这取决于您的应用程序,数据和问题域-而且仅一行代码-为什么会有“标准库函数”?
S.Lott

9
@美国洛特:allanymaxmin均为基本上单行,他们不只是在图书馆提供的,它们是内置的功能。因此,BDFL的原因并非如此。大多数人编写的一行代码相当复杂,通常不起作用,这是提供更好的东西的强烈理由。当然,任何提供其他策略的模块都必须提供警告,描述何时合适,更重要的是何时不合适。数值分析很困难,语言设计人员通常不尝试使用工具来帮助进行数字分析,这并不是很大的耻辱。
史蒂夫·杰索普

@史蒂夫·杰索普(Steve Jessop)。这些面向集合的功能没有浮点型的应用程序,数据和问题域依赖项。因此,“单线”显然不如真正的原因重要。数值分析很困难,并且不能成为通用语言库的一流组成部分。
S.Lott

6
@ S.Lott:如果标准的Python发行版没有为XML接口提供多个模块,我可能会同意。显然,不同的应用程序需要做一些不同的事情,这完全不妨碍将模块放在基础集中以一种或另一种方式来做。当然,有一些技巧可以比较经常重复使用的浮标,其中最基本的是指定数量的纸浆。因此,我仅部分同意-问题是数值分析很困难。Python 可以原则上,有时提供使它变得更容易的工具。我猜没有人自愿。
史蒂夫·杰索普

4
另外,“它归结为难以设计的一行代码”-如果正确执行后它仍然是单线的,那么我认为您的显示器要比我的显示器宽;-)。无论如何,我认为整个领域都是相当专业的,因为大多数程序员(包括我)很少使用它。加之艰辛,它不会成为大多数语言的核心库“最想要的”列表的顶部。
史蒂夫·杰索普

Answers:


323

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)

rel_tol是一个相对容差,它乘以两个参数中的较大者;当值变大时,它们之间的允许差异也会变大,同时仍将它们视为相等。

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


26
注意,当a或者bnumpy arraynumpy.isclose作品。
dbliss 2015年

6
@marsh rel_tol是一个相对容差,它乘以两个参数的较大值;当值变大时,它们之间的允许差异也会变大,同时仍将它们视为相等。abs_tol是在所有情况下均按原样应用的绝对公差。如果差异小于这些公差中的任何一个,则认为值相等。
马克·兰瑟姆

5
不要降低这个答案的价值(我认为这是一个很好的答案),值得注意的是该文档还说:“模错误检查等,该函数将返回...的结果”。换句话说,该isclose函数(以上)不是完整的实现。
rkersh '16

5
为恢复旧线程而道歉,但似乎值得指出的是,它isclose始终遵循不太保守的标准。我之所以仅提及它,是因为这种行为对我而言是违反直觉的。如果我要指定两个标准,我总是希望公差越小,而公差越大。
Mackie Messer

3
@MackieMesser您当然有权听取您的意见,但是这种行为对我来说是很合理的。根据您的定义,没有任何东西可以“接近”零,因为相对公差乘以零始终为零。
马克·兰瑟姆

71

如下简单的内容还不够好吗?

return abs(f1 - f2) <= allowed_error

8
正如我提供的链接所指出的那样,仅当您事先知道数字的近似大小时,才可以进行减法运算。
Gordon Wrigley

8
以我的经验,比较浮点数的最佳方法是:abs(f1-f2) < tol*max(abs(f1),abs(f2))。通常,这种相对公差是比较浮点数的唯一有意义的方法,因为它们通常受小数位后舍入误差的影响。
2015年

2
只需添加一个简单的示例,为什么它可能行不通:>>> abs(0.04 - 0.03) <= 0.01,它会产生False。我使用Python 2.7.10 [GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
schatten 2015年

3
@schatten是公平的,与特定的比较算法相比,该示例与机器二进制精度/格式有更多关系。当您在系统中输入0.03时,这实际上并不是进入CPU的数字。
安德鲁·怀特

2
该示例显示@AndrewWhite abs(f1 - f2) <= allowed_error无法正常工作。
schatten

44

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

但是我认为最好注意一下,如果您正在使用NumPy或正在考虑使用它,则可以使用打包功能。

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

不过有一点免责声明:根据您的平台,安装NumPy可能是不平凡的体验。


1
“根据您的平台,安装numpy可能是不平凡的体验。” ...嗯?在哪些平台上安装numpy是“不平凡的”?是什么使它变得平凡呢?
约翰

10
@John:很难获得Windows的64位二进制文​​件。pip在Windows上很难通过Numpy获得。
Ben Bolker'3

@Ternak:是的,但是我的一些学生使用Windows,所以我必须处理这些东西。
Ben Bolker,2015年

4
@BenBolker如果你有安装开放数据的科学平台,采用Python的,最好的办法就是蟒蛇continuum.io/downloads(大熊猫,numpy的多的开箱)
jrovegno

安装Anaconda并不
容易

13

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

从评论:

值得注意的是,如果您正在做大量的数学工作,并且您绝对不需要十进制的精度,那么这确实会使事情陷入困境。浮动是一种方式,处理起来更快,但不精确。小数非常精确,但速度很慢。


11

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


我不太了解您在这里所做的事情,但这很有趣。eps,eps1,eps2和EPS有什么区别?
Gordon Wrigley

eps1eps2定义一个相对和绝对的宽容:你准备让a,并b通过即将不同eps1时代有多大,他们都加上eps2eps是单个公差;你准备让a,并b通过即将不同的eps时候,他们是多么的大,条件是大小的东西EPS或更小被认为是规模EPS。如果将EPS其作为浮点类型的最小非正规值,则该值与Dawson的比较器非常相似(除了2 ^#bit的因数,因为Dawson会以ulps为单位测量容差)。
加雷斯·麦考恩

2
顺便说一句,我同意S. Lott的观点,Right Thing始终取决于您的实际应用程序,这就是为什么没有一个标准库函数可以满足您所有的浮点比较需求的原因。
Gareth McCaughan

@ gareth-mccaughan如何确定python的“浮点类型的最小非标准最小值”?
Gordon Wrigley

docs.python.org/tutorial/floatingpoint.html页面说几乎所有python实现都使用IEEE-754双精度浮点数,而en.wikipedia.org/wiki/IEEE_754-1985页面说最接近零的归一化数字是±2 * * -1022。
Gordon Wrigley

11

无法将浮点数进行相等性比较的常识是不正确的。浮点数与整数没有什么不同:如果计算“ a == b”,则它们是相同的数字将为true,否则为false(要理解两个NaN当然不是相同的数字)。

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

因此,假设我们问“如何比较整数是否相等?” 在这种情况下。没有一个答案。您应该做什么取决于具体情况,尤其是您遇到的错误类型以及要实现的错误类型。

这是一些可能的选择。

如果要在数学上精确的数字相等的情况下获得“真实”的结果,则可以尝试使用所执行的计算的属性来证明在两个数字中得到相同的错误。如果这是可行的,并且您比较了两个表达式所产生的两个数字,这些表达式在经过精确计算后将得出相等的数字,那么您将从比较中获得“ true”。另一种方法是,您可能会分析计算的属性,并证明误差不会超过特定数量,可能绝对值或相对于输入之一或输出之一的数量。在这种情况下,您可以询问两个计算得出的数字是否相差最大,如果在间隔内,则返回“ true”。如果您无法证明错误界限,您可能会猜测并希望达到最佳。猜测的一种方法是评估许多随机样本,并查看结果中得到的分布类型。

当然,由于我们仅设置了在数学上精确的结果相等的情况下您必须获得“真实”的要求,因此我们就保留了即使它们不相等也获得“真实”的可能性。(实际上,我们可以通过始终返回“ true”来满足要求。这使计算变得简单,但是通常不希望这样做,因此,我将在下面讨论改善这种情况。)

如果要在数学上精确的数字不相等的情况下获得“假”结果,则需要证明在数学上精确的数字不相等的情况下,对数字的评估会得出不同的数字。在许多常见情况下,出于实际目的这可能是不可能的。因此,让我们考虑一个替代方案。

一个有用的要求是,如果数学上精确的数字相差超过一定数量,我们将得到“假”结果。例如,也许我们要计算在计算机游戏中扔出的球在哪里移动,并且我们想知道它是否击中了蝙蝠。在这种情况下,如果球碰到球棒,我们当然想得到“ true”,如果球远离球棒,我们就希望得到“ false”,如果球进入球棒,我们可以接受不正确的“ true”答案。数学上精确的模拟未击中蝙蝠,但击中蝙蝠仅不到一毫米。在这种情况下,我们需要证明(或猜测/估计)我们对球的位置和球拍的位置的计算的组合误差最大为1毫米(对于所有感兴趣的位置)。这将使我们始终返回“

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

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

从那里继续进行变得更加复杂。下一步是执行其中一个输入已经有错误的操作。对于简单表达式,可以通过计算跟踪这些错误,以达到最终错误的界限。实际上,这仅在少数情况下才能完成,例如使用高质量的数学库。而且,当然,您需要精确地控制要执行的操作。高级语言通常会给编译器带来很多负担,因此您可能不知道以什么顺序执行操作。

关于这个话题还有很多(现在)可以写,但是我必须到此为止。总而言之,答案是:没有用于此比较的库例程,因为没有适合大多数需求的单一解决方案值得放入库例程中。(如果与一个相对误差间隔或绝对误差间隔进行比较就足够了,则无需库例程即可完成此操作。)


3
通过上面与Gareth McCaughan的讨论,正确地比较相对误差本质上等于“ abs(ab)<= eps max(2 * -1022,abs(a),abs(b))”,这不是我要描述的如此简单,当然我自己也无法解决。正如史蒂夫·杰索普(Steve Jessop)所指出的,它与max,min,any和all(全部内置)的复杂性相似。因此,在标准数学模块中提供相对误差比较似乎是个好主意。
Gordon Wrigley

(7/3 * 3 == 7 * 3/3)在python中评估为True。
xApple

@xApple:我刚在OS X 10.8.3上运行Python 2.7.2,然后输入(7/3*3 == 7*3/3)。它印了False
Eric Postpischil

3
您可能忘记了键入from __future__ import division。如果不这样做,则没有浮点数,并且比较是在两个整数之间。
xApple 2013年

3
这是一个重要的讨论,但没有帮助。
Dan Hulme

6

如果要在测试/ TDD上下文中使用它,我会说这是一种标准方式:

from nose.tools import assert_almost_equals

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

5

为此,math.isclose()添加到Python 3.5中(源代码)。这是它与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

2

我发现以下比较有帮助:

str(f1) == str(f2)

这很有趣,但是由于str(.1 + .2)== .3
Gordon Wrigley 2015年

str(.1 + .2)== str(.3)返回True
Henrikh Kantuni

这与f1 == f2有什么不同-如果它们都很接近但由于精度而仍然不同,则字符串表示形式也将不相等。
MrMas

2
.1 + .2 == .3返回False,而str(.1 + .2)== str(.3)返回True
克雷西米尔

4
在Python 3.7.2中,str(.1 + .2) == str(.3)返回False。上述方法仅适用于Python2。–
Danibix

1

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

有关详细信息,请参见“ 分数的分数”模块。


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

例:

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

封闭的无限概念不能很好地为您服务。isclose(1.0, 1.1)产生Falseisclose(0.1, 0.000000000001)返回True
kfsone

1

比较不超过给定十进制数的给定值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 

1

这也许有点丑陋,但是当您不需要的默认浮点精度(大约11位小数)时,它就可以很好地工作。

round_to函数使用格式方法从内置的str类的浮动四舍五入到代表浮球随所需的小数位数,然后用一个字符串应用EVAL内置功能圆形浮弦找回浮点数字类型。

is_close功能只适用于一个简单的条件向围捕浮动。

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

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

>>>a = 10.0
10.0
>>>b = 10.0001
10.0001
>>>print is_close(a, b, prec=3)
True
>>>print is_close(a, b, prec=4)
False

更新:

正如@stepehjfox所建议的那样,一种构建rount_to函数以避免“ eval”的更简洁方法是使用嵌套格式

def round_to(float_num, prec):
    return '{:.{precision}f}'.format(float_num, precision=prec)

遵循相同的思想,使用很棒的新f字符串(Python 3.6+),代码甚至可以变得更加简单:

def round_to(float_num, prec):
    return f'{float_num:.{prec}f}'

因此,我们甚至可以将其全部封装在一个简单干净的“ is_close”函数中:

def is_close(a, b, prec):
    return f'{a:.{prec}f}' == f'{b:.{prec}f}'

1
您不必使用eval()来获取参数化的格式。像return '{:.{precision}f'.format(float_num, precision=decimal_precision) 这样的事情 应该做
stephenjfox

1
我的评论和更多示例的来源:pyformat.info/#param_align
stephenjfox

1
谢谢@stephenjfox,我对嵌套格式一无所知。顺便说一句,您的示例代码缺少大括号:return '{:.{precision}}f'.format(float_num, precision=decimal_precision)
Albert Alomar

1
丰收,尤其是f弦的增强效果。随着Python 2的死亡指日可待,也许这将成为常态
stephenjfox

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.