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

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

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

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

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

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

It’s well known that comparing floats for equality is a little fiddly due to rounding and precision issues.

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

What is the recommended way to deal with this in Python?

Surely there is a standard library function for this somewhere?


回答 0

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

Python 3.5 adds the math.isclose and cmath.isclose functions as described in PEP 485.

If you’re using an earlier version of Python, the equivalent function is given in the 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 is a relative tolerance, it is multiplied by the greater of the magnitudes of the two arguments; as the values get larger, so does the allowed difference between them while still considering them equal.

abs_tol is an absolute tolerance that is applied as-is in all cases. If the difference is less than either of those tolerances, the values are considered equal.


回答 1

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

return abs(f1 - f2) <= allowed_error

Is something as simple as the following not good enough?

return abs(f1 - f2) <= allowed_error

回答 2

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

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

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

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

I would agree that Gareth’s answer is probably most appropriate as a lightweight function/solution.

But I thought it would be helpful to note that if you are using NumPy or are considering it, there is a packaged function for this.

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

A little disclaimer though: installing NumPy can be a non-trivial experience depending on your platform.


回答 3

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

从评论:

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

Use Python’s decimal module, which provides the Decimal class.

From the comments:

It is worth noting that if you’re doing math-heavy work and you don’t absolutely need the precision from decimal, this can really bog things down. Floats are way, way faster to deal with, but imprecise. Decimals are extremely precise but slow.


回答 4

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

I’m not aware of anything in the Python standard library (or elsewhere) that implements Dawson’s AlmostEqual2sComplement function. If that’s the sort of behaviour you want, you’ll have to implement it yourself. (In which case, rather than using Dawson’s clever bitwise hacks you’d probably do better to use more conventional tests of the form if abs(a-b) <= eps1*(abs(a)+abs(b)) + eps2 or similar. To get Dawson-like behaviour you might say something like if abs(a-b) <= eps*max(EPS,abs(a),abs(b)) for some small fixed EPS; this isn’t exactly the same as Dawson, but it’s similar in spirit.


回答 5

无法将浮点数进行相等性比较的常识是不正确的。浮点数与整数没有什么不同:如果计算“ 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之内的另一个数字。)

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

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

The common wisdom that floating-point numbers cannot be compared for equality is inaccurate. Floating-point numbers are no different from integers: If you evaluate “a == b”, you will get true if they are identical numbers and false otherwise (with the understanding that two NaNs are of course not identical numbers).

The actual problem is this: If I have done some calculations and am not sure the two numbers I have to compare are exactly correct, then what? This problem is the same for floating-point as it is for integers. If you evaluate the integer expression “7/3*3”, it will not compare equal to “7*3/3”.

So suppose we asked “How do I compare integers for equality?” in such a situation. There is no single answer; what you should do depends on the specific situation, notably what sort of errors you have and what you want to achieve.

Here are some possible choices.

If you want to get a “true” result if the mathematically exact numbers would be equal, then you might try to use the properties of the calculations you perform to prove that you get the same errors in the two numbers. If that is feasible, and you compare two numbers that result from expressions that would give equal numbers if computed exactly, then you will get “true” from the comparison. Another approach is that you might analyze the properties of the calculations and prove that the error never exceeds a certain amount, perhaps an absolute amount or an amount relative to one of the inputs or one of the outputs. In that case, you can ask whether the two calculated numbers differ by at most that amount, and return “true” if they are within the interval. If you cannot prove an error bound, you might guess and hope for the best. One way of guessing is to evaluate many random samples and see what sort of distribution you get in the results.

Of course, since we only set the requirement that you get “true” if the mathematically exact results are equal, we left open the possibility that you get “true” even if they are unequal. (In fact, we can satisfy the requirement by always returning “true”. This makes the calculation simple but is generally undesirable, so I will discuss improving the situation below.)

If you want to get a “false” result if the mathematically exact numbers would be unequal, you need to prove that your evaluation of the numbers yields different numbers if the mathematically exact numbers would be unequal. This may be impossible for practical purposes in many common situations. So let us consider an alternative.

A useful requirement might be that we get a “false” result if the mathematically exact numbers differ by more than a certain amount. For example, perhaps we are going to calculate where a ball thrown in a computer game traveled, and we want to know whether it struck a bat. In this case, we certainly want to get “true” if the ball strikes the bat, and we want to get “false” if the ball is far from the bat, and we can accept an incorrect “true” answer if the ball in a mathematically exact simulation missed the bat but is within a millimeter of hitting the bat. In that case, we need to prove (or guess/estimate) that our calculation of the ball’s position and the bat’s position have a combined error of at most one millimeter (for all positions of interest). This would allow us to always return “false” if the ball and bat are more than a millimeter apart, to return “true” if they touch, and to return “true” if they are close enough to be acceptable.

So, how you decide what to return when comparing floating-point numbers depends very much on your specific situation.

As to how you go about proving error bounds for calculations, that can be a complicated subject. Any floating-point implementation using the IEEE 754 standard in round-to-nearest mode returns the floating-point number nearest to the exact result for any basic operation (notably multiplication, division, addition, subtraction, square root). (In case of tie, round so the low bit is even.) (Be particularly careful about square root and division; your language implementation might use methods that do not conform to IEEE 754 for those.) Because of this requirement, we know the error in a single result is at most 1/2 of the value of the least significant bit. (If it were more, the rounding would have gone to a different number that is within 1/2 the value.)

Going on from there gets substantially more complicated; the next step is performing an operation where one of the inputs already has some error. For simple expressions, these errors can be followed through the calculations to reach a bound on the final error. In practice, this is only done in a few situations, such as working on a high-quality mathematics library. And, of course, you need precise control over exactly which operations are performed. High-level languages often give the compiler a lot of slack, so you might not know in which order operations are performed.

There is much more that could be (and is) written about this topic, but I have to stop there. In summary, the answer is: There is no library routine for this comparison because there is no single solution that fits most needs that is worth putting into a library routine. (If comparing with a relative or absolute error interval suffices for you, you can do it simply without a library routine.)


回答 6

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

from nose.tools import assert_almost_equals

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

If you want to use it in testing/TDD context, I’d say this is a standard way:

from nose.tools import assert_almost_equals

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

回答 7

为此,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

math.isclose() has been added to Python 3.5 for that (source code). Here is a port of it to Python 2. It’s difference from one-liner of Mark Ransom is that it can handle “inf” and “-inf” properly.

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

回答 8

我发现以下比较有帮助:

str(f1) == str(f2)

I found the following comparison helpful:

str(f1) == str(f2)

回答 9

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

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

For some of the cases where you can affect the source number representation, you can represent them as fractions instead of floats, using integer numerator and denominator. That way you can have exact comparisons.

See Fraction from fractions module for details.


回答 10

我喜欢@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))

I liked @Sesquipedal ‘s suggestion but with modification (a special use case when both values are 0 returns False). In my case I was on Python 2.7 and just used a simple function:

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

回答 11

对于要确保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

Useful for the case where you want to make sure 2 numbers are the same ‘up to precision’, no need to specify the tolerance:

  • Find minimum precision of the 2 numbers

  • Round both of them to minimum precision and compare

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)                               

As written, only works for numbers without the ‘e’ in their string representation ( meaning 0.9999999999995e-4 < number <= 0.9999999999995e11 )

Example:

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

回答 12

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

To compare up to a given decimal without 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 

回答 13

这也许有点丑陋,但是当您不需要的默认浮点精度(大约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}'

This maybe is a bit ugly hack, but it works pretty well when you don’t need more than the default float precision (about 11 decimals).

The round_to function uses the format method from the built-in str class to round up the float to a string that represents the float with the number of decimals needed, and then applies the eval built-in function to the rounded float string to get back to the float numeric type.

The is_close function just applies a simple conditional to the rounded up float.

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

Update:

As suggested by @stepehjfox, a cleaner way to build a rount_to function avoiding “eval” is using nested formatting:

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

Following the same idea, the code can be even simpler using the great new f-strings (Python 3.6+):

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

So, we could even wrap it up all in one simple and clean ‘is_close’ function:

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

回答 14

就绝对误差而言,您只需检查一下

if abs(a - b) <= error:
    print("Almost equal")

为什么float行为在Python中很奇怪的一些信息 https://youtu.be/v4HhvoNLILk?t=1129

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

In terms of absolute error, you can just check

if abs(a - b) <= error:
    print("Almost equal")

Some information of why float act weird in Python https://youtu.be/v4HhvoNLILk?t=1129

You can also use math.isclose for relative errors