Update PEP 485 from Chris Barker's edits (w/ minor formatting)
This commit is contained in:
parent
b6fa3e2e91
commit
2ca23b9a32
190
pep-0485.txt
190
pep-0485.txt
|
@ -16,16 +16,14 @@ Abstract
|
|||
|
||||
This PEP proposes the addition of a function to the standard library
|
||||
that determines whether one value is approximately equal or "close" to
|
||||
another value. It is also proposed that an assertion be added to the
|
||||
``unittest.TestCase`` class to provide easy access for those using
|
||||
unittest for testing.
|
||||
another value.
|
||||
|
||||
|
||||
Rationale
|
||||
=========
|
||||
|
||||
Floating point values contain limited precision, which results in
|
||||
their being unable to exactly represent some values, and for error to
|
||||
their being unable to exactly represent some values, and for errors to
|
||||
accumulate with repeated computation. As a result, it is common
|
||||
advice to only use an equality comparison in very specific situations.
|
||||
Often a inequality comparison fits the bill, but there are times
|
||||
|
@ -49,8 +47,8 @@ method, but it:
|
|||
|
||||
* Is an absolute difference test. Often the measure of difference
|
||||
requires, particularly for floating point numbers, a relative error,
|
||||
i.e "Are these two values within x% of each-other?", rather than an
|
||||
absolute error. Particularly when the magnatude of the values is
|
||||
i.e. "Are these two values within x% of each-other?", rather than an
|
||||
absolute error. Particularly when the magnitude of the values is
|
||||
unknown a priori.
|
||||
|
||||
The numpy package has the ``allclose()`` and ``isclose()`` functions,
|
||||
|
@ -63,7 +61,7 @@ One can also find discussion and sample implementations on Stack
|
|||
Overflow and other help sites.
|
||||
|
||||
Many other non-python systems provide such a test, including the Boost C++
|
||||
library and the APL language (reference?).
|
||||
library and the APL language [4]_.
|
||||
|
||||
These existing implementations indicate that this is a common need and
|
||||
not trivial to write oneself, making it a candidate for the standard
|
||||
|
@ -73,28 +71,31 @@ library.
|
|||
Proposed Implementation
|
||||
=======================
|
||||
|
||||
NOTE: this PEP is the result of an extended discussion on the
|
||||
NOTE: this PEP is the result of extended discussions on the
|
||||
python-ideas list [1]_.
|
||||
|
||||
The new function will have the following signature::
|
||||
The new function will go into the math module, and have the following
|
||||
signature::
|
||||
|
||||
is_close(a, b, rel_tolerance=1e-9, abs_tolerance=0.0)
|
||||
isclose(a, b, rel_tol=1e-9, abs_tol=0.0)
|
||||
|
||||
``a`` and ``b``: are the two values to be tested to relative closeness
|
||||
|
||||
``rel_tolerance``: is the relative tolerance -- it is the amount of
|
||||
error allowed, relative to the magnitude a and b. For example, to set
|
||||
a tolerance of 5%, pass tol=0.05. The default tolerance is 1e-8, which
|
||||
assures that the two values are the same within about 8 decimal
|
||||
digits.
|
||||
``rel_tol``: is the relative tolerance -- it is the amount of error
|
||||
allowed, relative to the larger absolute value of a or b. For example,
|
||||
to set a tolerance of 5%, pass tol=0.05. The default tolerance is 1e-9,
|
||||
which assures that the two values are the same within about 9 decimal
|
||||
digits. ``rel_tol`` must be greater than 0.0
|
||||
|
||||
``abs_tolerance``: is an minimum absolute tolerance level -- useful for
|
||||
``abs_tol``: is an minimum absolute tolerance level -- useful for
|
||||
comparisons near zero.
|
||||
|
||||
Modulo error checking, etc, the function will return the result of::
|
||||
|
||||
abs(a-b) <= max( rel_tolerance * min(abs(a), abs(b), abs_tolerance )
|
||||
abs(a-b) <= max( rel_tol * max(abs(a), abs(b)), abs_tol )
|
||||
|
||||
The name, ``isclose``, is selected for consistency with the existing
|
||||
``isnan`` and ``isinf``.
|
||||
|
||||
Handling of non-finite numbers
|
||||
------------------------------
|
||||
|
@ -111,17 +112,18 @@ Non-float types
|
|||
The primary use-case is expected to be floating point numbers.
|
||||
However, users may want to compare other numeric types similarly. In
|
||||
theory, it should work for any type that supports ``abs()``,
|
||||
comparisons, and subtraction. The code will be written and tested to
|
||||
accommodate these types:
|
||||
multiplication, comparisons, and subtraction. The code will be written
|
||||
and tested to accommodate these types:
|
||||
|
||||
* ``Decimal``: for Decimal, the tolerance must be set to a Decimal type.
|
||||
* ``Decimal``
|
||||
|
||||
* ``int``
|
||||
|
||||
* ``Fraction``
|
||||
|
||||
* ``complex``: for complex, ``abs(z)`` will be used for scaling and
|
||||
comparison.
|
||||
* ``complex``: for complex, the absolute value of the complex values
|
||||
will be used for scaling and comparison. If a complex tolerance is
|
||||
passed in, the absolute value will be used as the tolerance.
|
||||
|
||||
|
||||
Behavior near zero
|
||||
|
@ -130,9 +132,9 @@ 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_tolerance
|
||||
times that value. rel-tolerance is always less than one, so the
|
||||
difference will never be less than the tolerance.
|
||||
other value, and the computed absolute tolerance will be ``rel_tol``
|
||||
times that value. When ``rel_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
|
||||
|
@ -146,50 +148,51 @@ 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_tolerance`` can be
|
||||
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 absolute tolerance. That is, the values will be always be
|
||||
considered close if the difference between them is less than the
|
||||
abs_tolerance
|
||||
computed relative tolerance. That is, the values will be always be
|
||||
considered close if the difference between them is less than
|
||||
``abs_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-8 might be appropriate, but that would be far
|
||||
too large if expected values are on order of 1e-12 or smaller.
|
||||
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
|
||||
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 ``all_close()`` function, which provides
|
||||
a default abs_tolerance, and make sure that the default value is
|
||||
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_tolerance parameter to 0.0, then only the
|
||||
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.
|
||||
|
||||
unittest assertion
|
||||
-------------------
|
||||
|
||||
[need text here]
|
||||
|
||||
implementation
|
||||
Implementation
|
||||
--------------
|
||||
|
||||
A sample implementation is available (as of Jan 22, 2015) on gitHub:
|
||||
|
||||
https://github.com/PythonCHB/close_pep/blob/master
|
||||
https://github.com/PythonCHB/close_pep/blob/master/is_close.py
|
||||
|
||||
This implementation has a flag that lets the user select which
|
||||
relative tolerance test to apply -- this PEP does not suggest that
|
||||
that be retained, but rather than the strong test be selected.
|
||||
that be retained, but rather than the weak test be selected.
|
||||
|
||||
There are also drafts of this PEP and test code, etc. there:
|
||||
|
||||
https://github.com/PythonCHB/close_pep
|
||||
|
||||
|
||||
Relative Difference
|
||||
===================
|
||||
|
@ -215,7 +218,7 @@ consideration, for instance:
|
|||
|
||||
4) The absolute value of the arithmetic mean of the two
|
||||
|
||||
These lead to the following possibilities for determining if two
|
||||
These leads to the following possibilities for determining if two
|
||||
values, a and b, are close to each other.
|
||||
|
||||
1) ``abs(a-b) <= tol*abs(a)``
|
||||
|
@ -228,9 +231,9 @@ values, a and b, are close to each other.
|
|||
|
||||
NOTE: (2) and (3) can also be written as:
|
||||
|
||||
2) ``(abs(a-b) <= tol*abs(a)) or (abs(a-b) <= tol*abs(a))``
|
||||
2) ``(abs(a-b) <= abs(tol*a)) or (abs(a-b) <= abs(tol*b))``
|
||||
|
||||
3) ``(abs(a-b) <= tol*abs(a)) and (abs(a-b) <= tol*abs(a))``
|
||||
3) ``(abs(a-b) <= abs(tol*a)) and (abs(a-b) <= abs(tol*b))``
|
||||
|
||||
(Boost refers to these as the "weak" and "strong" formulations [3]_)
|
||||
These can be a tiny bit more computationally efficient, and thus are
|
||||
|
@ -292,7 +295,7 @@ maximum tolerance ``tol * a == 0.1 * 10 == 1.0``
|
|||
|
||||
minimum tolerance ``tol * b == 0.1 * 9.0 == 0.9``
|
||||
|
||||
delta = ``(1.0 - 0.9) * 0.1 = 0.1`` or ``tol**2 * a = 0.1**2 * 10 = .01``
|
||||
delta = ``(1.0 - 0.9) = 0.1`` or ``tol**2 * a = 0.1**2 * 10 = .1``
|
||||
|
||||
The absolute difference between the maximum and minimum tolerance
|
||||
tests in this case could be substantial. However, the primary use
|
||||
|
@ -310,7 +313,7 @@ precision of floating point. That is, each of the four methods will
|
|||
yield exactly the same results for all values of a and b.
|
||||
|
||||
In addition, in common use, tolerances are defined to 1 significant
|
||||
figure -- that is, 1e-8 is specifying about 8 decimal digits of
|
||||
figure -- that is, 1e-9 is specifying about 9 decimal digits of
|
||||
accuracy. So the difference between the various possible tests is well
|
||||
below the precision to which the tolerance is specified.
|
||||
|
||||
|
@ -321,11 +324,11 @@ Symmetry
|
|||
A relative comparison can be either symmetric or non-symmetric. For a
|
||||
symmetric algorithm:
|
||||
|
||||
``is_close_to(a,b)`` is always the same as ``is_close_to(b,a)``
|
||||
``isclose(a,b)`` is always the same as ``isclose(b,a)``
|
||||
|
||||
If a relative closeness test uses only one of the values (such as (1)
|
||||
above), then the result is asymmetric, i.e. is_close_to(a,b) is not
|
||||
necessarily the same as is_close_to(b,a).
|
||||
above), then the result is asymmetric, i.e. isclose(a,b) is not
|
||||
necessarily the same as isclose(b,a).
|
||||
|
||||
Which approach is most appropriate depends on what question is being
|
||||
asked. If the question is: "are these two numbers close to each
|
||||
|
@ -369,18 +372,31 @@ The case that uses the arithmetic mean of the two values requires that
|
|||
the value be either added together before dividing by 2, which could
|
||||
result in extra overflow to inf for very large numbers, or require
|
||||
each value to be divided by two before being added together, which
|
||||
could result in underflow to -inf for very small numbers. This effect
|
||||
could result in underflow to zero for very small numbers. This effect
|
||||
would only occur at the very limit of float values, but it was decided
|
||||
there as no benefit to the method worth reducing the range of
|
||||
functionality.
|
||||
there was no benefit to the method worth reducing the range of
|
||||
functionality or adding the complexity of checking values to determine
|
||||
the order of computation.
|
||||
|
||||
This leaves the boost "weak" test (2)-- or using the smaller value to
|
||||
scale the tolerance, or the Boost "strong" (3) test, which uses the
|
||||
smaller of the values to scale the tolerance. For small tolerance,
|
||||
they yield the same result, but this proposal uses the boost "strong"
|
||||
test case: it is symmetric and provides a slightly stricter criteria
|
||||
for tolerance.
|
||||
they yield the same result, but this proposal uses the boost "weak"
|
||||
test case: it is symmetric and provides a more useful result for very
|
||||
large tolerances.
|
||||
|
||||
Large tolerances
|
||||
----------------
|
||||
|
||||
The most common use case is expected to be small tolerances -- on order of the
|
||||
default 1e-9. However there may be use cases where a user wants to know if two
|
||||
fairly disparate values are within a particular range of each other: "is a
|
||||
within 200% (rel_tol = 2.0) of b? In this case, the string test would never
|
||||
indicate that two values are within that range of each other if one of them is
|
||||
zero. The strong case, however would use the larger (non-zero) value for the
|
||||
test, and thus return true if one value is zero. For example: is 0 within 200%
|
||||
of 10? 200% of ten is 20, so the range within 200% of ten is -10 to +30. Zero
|
||||
falls within that range, so it will return True.
|
||||
|
||||
Defaults
|
||||
========
|
||||
|
@ -392,16 +408,15 @@ Relative Tolerance Default
|
|||
|
||||
The relative tolerance required for two values to be considered
|
||||
"close" is entirely use-case dependent. Nevertheless, the relative
|
||||
tolerance needs to be less than 1.0, and greater than 1e-16
|
||||
(approximate precision of a python float). The value of 1e-9 was
|
||||
selected because it is the largest relative tolerance for which the
|
||||
various possible methods will yield the same result, and it is also
|
||||
about half of the precision available to a python float. In the
|
||||
general case, a good numerical algorithm is not expected to lose more
|
||||
than about half of available digits of accuracy, and if a much larger
|
||||
tolerance is acceptable, the user should be considering the proper
|
||||
value in that case. Thus 1-e9 is expected to "just work" for many
|
||||
cases.
|
||||
tolerance needs to be greater than 1e-16 (approximate precision of a
|
||||
python float). The value of 1e-9 was selected because it is the
|
||||
largest relative tolerance for which the various possible methods will
|
||||
yield the same result, and it is also about half of the precision
|
||||
available to a python float. In the general case, a good numerical
|
||||
algorithm is not expected to lose more than about half of available
|
||||
digits of accuracy, and if a much larger tolerance is acceptable, the
|
||||
user should be considering the proper value in that case. Thus 1-e9 is
|
||||
expected to "just work" for many cases.
|
||||
|
||||
Absolute tolerance default
|
||||
--------------------------
|
||||
|
@ -437,19 +452,16 @@ The primary expected use case is various forms of testing -- "are the
|
|||
results computed near what I expect as a result?" This sort of test
|
||||
may or may not be part of a formal unit testing suite. Such testing
|
||||
could be used one-off at the command line, in an iPython notebook,
|
||||
part of doctests, or simple assets in an ``if __name__ == "__main__"``
|
||||
part of doctests, or simple asserts in an ``if __name__ == "__main__"``
|
||||
block.
|
||||
|
||||
The proposed unitest.TestCase assertion would have course be used in
|
||||
unit testing.
|
||||
|
||||
It would also be an appropriate function to use for the termination
|
||||
criteria for a simple iterative solution to an implicit function::
|
||||
|
||||
guess = something
|
||||
while True:
|
||||
new_guess = implicit_function(guess, *args)
|
||||
if is_close(new_guess, guess):
|
||||
if isclose(new_guess, guess):
|
||||
break
|
||||
guess = new_guess
|
||||
|
||||
|
@ -481,13 +493,13 @@ places (default 7), and comparing to zero.
|
|||
This method is purely an absolute tolerance test, and does not address
|
||||
the need for a relative tolerance test.
|
||||
|
||||
numpy ``is_close()``
|
||||
--------------------
|
||||
numpy ``isclose()``
|
||||
-------------------
|
||||
|
||||
http://docs.scipy.org/doc/numpy-dev/reference/generated/numpy.isclose.html
|
||||
|
||||
The numpy package provides the vectorized functions is_close() and
|
||||
all_close, for similar use cases as this proposal:
|
||||
The numpy package provides the vectorized functions isclose() and
|
||||
allclose(), for similar use cases as this proposal:
|
||||
|
||||
``isclose(a, b, rtol=1e-05, atol=1e-08, equal_nan=False)``
|
||||
|
||||
|
@ -522,9 +534,9 @@ Boost floating-point comparison
|
|||
-------------------------------
|
||||
|
||||
The Boost project ( [3]_ ) provides a floating point comparison
|
||||
function. Is is a symmetric approach, with both "weak" (larger of the
|
||||
function. It is a symmetric approach, with both "weak" (larger of the
|
||||
two relative errors) and "strong" (smaller of the two relative errors)
|
||||
options. This proposal uses the Boost "strong" approach. There is no
|
||||
options. This proposal uses the Boost "weak" approach. There is no
|
||||
need to complicate the API by providing the option to select different
|
||||
methods when the results will be similar in most cases, and the user
|
||||
is unlikely to know which to select in any case.
|
||||
|
@ -545,10 +557,6 @@ most appropriate. However, that would require anyone needing such a
|
|||
test to, at the very least, copy the function into their code base,
|
||||
and select the comparison method to use.
|
||||
|
||||
In addition, adding the function to the standard library allows it to
|
||||
be used in the ``unittest.TestCase.assertIsClose()`` method, providing
|
||||
a substantial convenience to those using unittest.
|
||||
|
||||
|
||||
``zero_tol``
|
||||
''''''''''''
|
||||
|
@ -568,16 +576,15 @@ No absolute tolerance
|
|||
'''''''''''''''''''''
|
||||
|
||||
Given the issues with comparing to zero, another possibility would
|
||||
have been to only provide a relative tolerance, and let every
|
||||
comparison to zero fail. In this case, the user would need to do a
|
||||
simple absolute test: `abs(val) < zero_tol` in the case where the
|
||||
comparison involved zero.
|
||||
have been to only provide a relative tolerance, and let comparison to
|
||||
zero fail. In this case, the user would need to do a simple absolute
|
||||
test: `abs(val) < zero_tol` in the case where the comparison involved
|
||||
zero.
|
||||
|
||||
However, this would not allow the same call to be used for a sequence
|
||||
of values, such as in a loop or comprehension, or in the
|
||||
``TestCase.assertClose()`` method. Making the function far less
|
||||
useful. It is noted that the default abs_tolerance=0.0 achieves the
|
||||
same effect if the default is not overidden.
|
||||
of values, such as in a loop or comprehension. Making the function far
|
||||
less useful. It is noted that the default abs_tol=0.0 achieves the
|
||||
same effect if the default is not overridden.
|
||||
|
||||
Other tests
|
||||
''''''''''''
|
||||
|
@ -605,6 +612,11 @@ References
|
|||
|
||||
http://www.boost.org/doc/libs/1_35_0/libs/test/doc/components/test_tools/floating_point_comparison.html
|
||||
|
||||
.. [4] 1976. R. H. Lathwell. APL comparison tolerance. Proceedings of
|
||||
the eighth international conference on APL Pages 255 - 258
|
||||
|
||||
http://dl.acm.org/citation.cfm?doid=800114.803685
|
||||
|
||||
.. Bruce Dawson's discussion of floating point.
|
||||
|
||||
https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/
|
||||
|
|
Loading…
Reference in New Issue