math.isclose() 的参数 rel_tol 和 abs_tol
math.isclose() 函数
今天用到 math.isclose()
函数,发现这个函数有 2 个参数: rel_tol
, abs_tol
,首先查看官方文档:
math.isclose
(a, b, *, rel_tol=1e-09, abs_tol=0.0)Return
True
if the values a and b are close to each other andFalse
otherwise.Whether or not two values are considered close is determined according to given absolute and relative tolerances.
rel_tol is the relative tolerance – it is the maximum allowed difference between a and b, relative to the larger absolute value of a or b. For example, to set a tolerance of 5%, pass
rel_tol=0.05
. The default tolerance is1e-09
, which assures that the two values are the same within about 9 decimal digits. rel_tol must be greater than zero.abs_tol is the minimum absolute tolerance – useful for comparisons near zero. abs_tol must be at least zero.
If no errors occur, the result will be:
abs(a-b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol)
.The IEEE 754 special values of
NaN
,inf
, and-inf
will be handled according to IEEE rules. Specifically,NaN
is not considered close to any other value, includingNaN
.inf
and-inf
are only considered close to themselves.New in version 3.5.
See also: PEP 485 – A function for testing approximate equality
官方文档给出了精确的描述:abs(a-b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol)
.,有了这个公式,就可以知道实际的比较过程,然后匹配需要的参数了。比如我想知道某个数是不是和 9
相近,绝对误差范围在 1e-6
内,就可以用 isclose(num, 9, abs_tol=1e-6)
参数解读
那么问题就来了,为什么会有 2 个参数呢?默认给出的是相对误差,而绝对误差的默认值是 0 ,这背后的原因是什么?实际应用又该如何设置呢?这些问题可以在 PEP 485 里找到答案。
Behavior near zero
Relative comparison is problematic if either value is zero. By definition, no value is small relative to zero. And computationally, if either value is zero, the difference is the absolute value of the other value, and the computed absolute tolerance will be
rel_tol
times that value. Whenrel_tol
is less than one, the difference will never be less than the tolerance.However, while mathematically correct, there are many use cases where a user will need to know if a computed value is "close" to zero. This calls for an absolute tolerance test. If the user needs to call this function inside a loop or comprehension, where some, but not all, of the expected values may be zero, it is important that both a relative tolerance and absolute tolerance can be tested for with a single function with a single set of parameters.
There is a similar issue if the two values to be compared straddle zero: if a is approximately equal to -b, then a and b will never be computed as "close".
To handle this case, an optional parameter,
abs_tol
can be used to set a minimum tolerance used in the case of very small or zero computed relative tolerance. That is, the values will be always be considered close if the difference between them is less thanabs_tol
The default absolute tolerance value is set to zero because there is no value that is appropriate for the general case. It is impossible to know an appropriate value without knowing the likely values expected for a given use case. If all the values tested are on order of one, then a value of about 1e-9 might be appropriate, but that would be far too large if expected values are on order of 1e-9 or smaller.
Any non-zero default might result in user's tests passing totally inappropriately. If, on the other hand, a test against zero fails the first time with defaults, a user will be prompted to select an appropriate value for the problem at hand in order to get the test to pass.
NOTE: that the author of this PEP has resolved to go back over many of his tests that use the numpy
allclose()
function, which provides a default absolute tolerance, and make sure that the default value is appropriate.If the user sets the rel_tol parameter to 0.0, then only the absolute tolerance will effect the result. While not the goal of the function, it does allow it to be used as a purely absolute tolerance check as well.
我来简单概括一下:当只给出 rel_tol
时,数值比较可能会出问题。出问题时需要满足的条件有 2 个:
- rel_tol < 1
- 任意一个被比较的数值为 0
举个栗子:isclose(0.0, 1e-100, rel_tol=1e-9, abs_tol=0.0)
,这就是默认的参数,带入公式 abs(a-b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol)
可以得到 1e-100 <= 1e-109
, 输出 False
,出问题了!此时不论 b 是多少,公式变成了 b <= rel_tol * b
,因此结果一定是 False
。另外,当 a, b 一个是正数,另一个是负数时,可以带一下公式,也是一样的结果。
为了解决这个问题,引入了 abs_tol
,这样就可以保证得到期望的结果。
那么,为什么 abs_tol
的默认值是 0 呢?因为 abs_tol
的值和被比较数据的数量级有关,如果比较数量级为 1 的数,1e-9 就挺好,而如果数量级是 1e-9 ,再把 1e-9 作为误差,它就太大了。
如果 abs_tol
的默认值不为 0 ,使用场景那么复杂,一定会有某个用户不适用。而默认值设置成 0 ,引发比较错误,就相当于提示用户,需要修改这个参数,这样就保证了 abs_tol
一定是最佳的值。(这确实很 pythonic ...)
NOTE 里写的是 PEP 的作者回去检查他的参数去了,他用的 numpy 的 allclose()
,这个函数有默认的 "absolute tolerance",可以从 allclose()
的官方文档 上看到: atol=1e-08
。(这确实是个坑啊)
当然,如果把 rel_tol
设置成 0 ,只给 abs_tol
也是可以用的,不过设计者的初衷不是这样的。
然后也大概看了一眼这个函数提出来的过程,参考 [Python-ideas] Way to check for floating point "closeness"? ,顺着 Next message 一路点下去就能看到讨论的全过程,设计者真是用心了,在这个问题上考虑各种使用场景,还去参考别的语言的实现……