如何在 Python 中比较浮点数是否几乎相等?
- 2024-11-21 08:33:00
- admin 原创
- 6
问题描述:
众所周知,由于舍入和精度问题,比较浮点数是否相等有点麻烦。有关这方面的示例,请参阅Bruce Dawson 的博客文章《比较浮点数,2012 版》 。
我该如何在 Python 中处理这个问题?
某个地方是否有可用的标准库函数?
解决方案 1:
Python 3.5 添加了math.isclose
和cmath.isclose
函数,如PEP 485中所述。
如果您使用的是早期版本的 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
是绝对公差,适用于所有情况。如果差值小于任一公差,则认为值相等。
解决方案 2:
像下面这样简单的事情可能就足够了:
return abs(f1 - f2) <= allowed_error
解决方案 3:
我同意 Gareth 的答案可能最适合作为轻量级函数/解决方案。
但我认为值得注意的是,如果您正在使用 NumPy 或者正在考虑使用它,那么有一个适用于它的打包函数。
numpy.isclose(a, b, rtol=1e-05, atol=1e-08, equal_nan=False)
不过需要注意一点:根据您的平台,安装 NumPy 可能会是一个不简单的体验。
解决方案 4:
使用 Python 的decimal
模块,它提供了Decimal
类。
来自评论:
值得注意的是,如果你正在做数学繁重的工作,而且你并不绝对需要十进制的精度,那么这真的会让事情变得很慢。浮点数的处理速度要快得多,但不精确。小数非常精确,但速度很慢。
解决方案 5:
为此,Python 3.5 中已添加了math.isclose() (源代码)。这是将其移植到 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://github.com/python/cpython/blob/v3.5.10/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
解决方案 6:
浮点数不能进行比较以确定相等性这一常识是不准确的。浮点数与整数没有区别:如果您计算“a == b”,如果它们是相同的数字,则结果为 true,否则为 false(当然,两个 NaN 不是相同的数字)。
实际问题是这样的:如果我做了一些计算,但不确定要比较的两个数字是否完全正确,那该怎么办?这个问题对于浮点数和整数来说都是一样的。如果您计算整数表达式“7/33”,它不会与“73/3”相等。
因此,假设我们在这种情况下问“如何比较整数是否相等?”没有唯一的答案;您应该做什么取决于具体情况,特别是您遇到了什么类型的错误以及您想要实现什么。
以下是一些可能的选择。
如果希望在数学上精确的数字相等时得到“真”结果,那么您可以尝试使用您执行的计算的属性来证明您在两个数字中得到的误差相同。如果这是可行的,并且您比较两个数字,这两个数字是由表达式得出的,如果精确计算,这两个表达式将给出相等的数字,那么您将从比较中得到“真”。另一种方法是,您可以分析计算的属性并证明误差永远不会超过一定量,可能是绝对量或相对于输入或输出的量。在这种情况下,您可以询问两个计算出的数字是否最多相差该量,如果它们在间隔内,则返回“真”。如果您无法证明误差界限,您可以猜测并希望得到最好的结果。一种猜测的方法是评估许多随机样本,看看您在结果中得到什么样的分布。
当然,由于我们只设定了在数学上精确的结果相等时才得到“真”的要求,因此即使结果不相等,我们仍然保留得到“真”的可能性。(事实上,我们可以通过始终返回“真”来满足要求。这使计算变得简单,但通常是不可取的,因此我将在下面讨论如何改进这种情况。)
如果希望在数学上精确的数字不相等时得到“错误”结果,则需要证明在数学上精确的数字不相等时,您对数字的评估会产生不同的数字。在许多常见情况下,这在实际应用中可能是不可能的。所以让我们考虑一种替代方案。
一个有用的要求可能是,如果数学上精确的数字相差超过一定量,我们就会得到“假”结果。例如,也许我们要计算电脑游戏中投掷的球的飞行路线,并且我们想知道它是否击中了球棒。在这种情况下,如果球击中了球棒,我们当然希望得到“真”,如果球离球棒很远,我们想要得到“假”,如果数学上精确的模拟中球没有击中球棒,但距离击中球棒只有一毫米,我们可以接受错误的“真”答案。在这种情况下,我们需要证明(或猜测/估计)我们对球的位置和球棒位置的计算总误差最多为一毫米(对于所有感兴趣的位置)。这将使我们能够在球和球棒相距一毫米以上时始终返回“假”,在它们接触时返回“真”,在它们足够接近以致于可以接受时返回“真”。
因此,在比较浮点数时如何决定返回什么在很大程度上取决于您的具体情况。
至于如何证明计算的误差界限,这可能是一个复杂的问题。任何使用 IEEE 754 标准以四舍五入模式的浮点实现都会返回最接近任何基本运算(特别是乘法、除法、加法、减法、平方根)的精确结果的浮点数。(如果出现平局,则四舍五入以使低位为偶数。)(对平方根和除法要特别小心;您的语言实现可能会使用不符合 IEEE 754 的方法。)由于这个要求,我们知道单个结果中的错误最多是最低有效位值的 1/2。(如果错误更多,则四舍五入将变为在 1/2 值范围内的其他数字。)
接下来的事情就复杂得多了;下一步是执行一个操作,其中一个输入已经有一些错误。对于简单的表达式,可以通过计算跟踪这些错误,以达到最终错误的界限。实际上,这只在少数情况下完成,例如在高质量的数学库上工作。当然,你需要精确控制到底执行哪些操作。高级语言通常给编译器很大的自由度,所以你可能不知道操作执行的顺序。
关于这个主题还有很多可以(而且已经)写的内容,但我必须就此打住。总之,答案是:没有用于此比较的库例程,因为没有适合大多数需求的单一解决方案值得放入库例程中。(如果使用相对或绝对误差区间进行比较对您来说就足够了,那么您可以简单地进行,而无需使用库例程。)
解决方案 7:
我不知道 Python 标准库(或其他地方)中是否有实现 DawsonAlmostEqual2sComplement
函数的内容。如果这是您想要的行为,则必须自己实现。(在这种情况下,与其使用 Dawson 巧妙的按位黑客,您最好使用形式或类似的更传统的测试。要获得类似 Dawson 的行为,您可能会对一些固定的小值if abs(a-b) <= eps1*(abs(a)+abs(b)) + eps2
进行类似的说法;这与 Dawson 并不完全相同,但本质上是相似的。if abs(a-b) <= eps*max(EPS,abs(a),abs(b))
`EPS`
解决方案 8:
如果你想在测试/ TDD环境中使用它,我会说这是一种标准方法:
from nose.tools import assert_almost_equals
assert_almost_equals(x, y, places=7) # The default is 7
解决方案 9:
就绝对误差而言,你可以检查
if abs(a - b) <= error:
print("Almost equal")
有关为什么浮点数在 Python 中表现异常的一些信息:
Python 3 教程 03 - if-else、逻辑运算符和初学者最常犯的错误
您还可以使用math.isclose来获取相对误差。
解决方案 10:
这对于您想要确保两个数字“精度”相同,且无需指定公差的情况很有用:
找到两个数字的最小精度
将它们四舍五入到最小精度并进行比较
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.999999999995e11)
例子:
>>> isclose(10.0, 10.049)
True
>>> isclose(10.0, 10.05)
False
解决方案 11:
对于某些可以影响源数字表示的情况,您可以使用整数分子和分母将它们表示为分数而不是浮点数。这样您就可以进行精确的比较。
有关详细信息,请参阅分数模块中的分数。
解决方案 12:
我发现以下比较很有帮助:
str(f1) == str(f2)
解决方案 13:
如果您想使用该pytest
包在测试或 TDD 环境中执行此操作,请按以下步骤操作:
import pytest
PRECISION = 1e-3
def assert_almost_equal():
obtained_value = 99.99
expected_value = 100.00
assert obtained_value == pytest.approx(expected_value, PRECISION)
解决方案 14:
我喜欢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))
解决方案 15:
要比较至多给定的小数,无需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
解决方案 16:
这可能是一个有点丑陋的黑客,但当你不需要超过默认浮点精度(大约 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所建议的,构建避免“eval”的rount_to函数的更清洁的方法是使用嵌套格式:
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}'
解决方案 17:
如果您想比较浮点数,上述选项都很棒,但就我而言,我最终使用了枚举,因为我的用例只接受几个有效的浮点数。
from enum import Enum
class HolidayMultipliers(Enum):
EMPLOYED_LESS_THAN_YEAR = 2.0
EMPLOYED_MORE_THAN_YEAR = 2.5
然后运行:
testable_value = 2.0
HolidayMultipliers(testable_value)
如果浮点数有效,那就没问题,否则它只会抛出一个ValueError
。
解决方案 18:
==
如果您不关心容忍度,则使用是一种简单的好方法。
# 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__
。
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理必备:盘点2024年13款好用的项目管理软件