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 一路點下去就能看到討論的全過程,設計者真是用心了,在這個問題上考慮各種使用場景,還去參考別的語言的實現……