[Python-ideas] Way to check for floating point "closeness"?
Steven D'Aprano
steve at pearwood.info
Sat Jan 17 11:01:18 CET 2015
On Fri, Jan 16, 2015 at 11:09:16PM -0500, Terry Reedy wrote:
> On 1/12/2015 12:02 PM, Chris Barker wrote:
> >but it:
> >
> >A) is buried in the unittest.TestCase class
> >
> >B) is an assertion, so you can't use it as a general test (easily)
> >
> >C) uses number of decimal digits or an absolute delta, but does not
> >provide a significant figures comparison, which is most likely what's
> >wanted (and a bit harder to write yourself)
>
> assertAlmostEqual((a-b)/d, 0, delta = tol)
> where d is a, b, and (a+b)/2 as one thinks is appropriate.
That does nothing to solve problems A) and B), and I'm dubious that it
provides a "significant figures comparison" (whatever that means, I'm
pretty sure it doesn't mean "relative error", which is what you're
calculating in a round-about fashion).
> >numpy provides allclose()
>
> According to Neil Girdhar,
> absolute(/a/ - /b/) <= (/atol/ + /rtol/ * absolute(/b/))
> which I presume means, in Python,
> abs(a-b) <= atol + rtol * abs(b)
> where atol and rtol are assume >= 0.0
Adding the error tolerances together is a dubious thing to do. I don't
understand the reasoning between that. Given two values a and b, there
are two definitions of the error between them:
absolute = abs(a - b)
relative = abs(a - b)/abs(b)
[Note: I'm sticking to numpy's unconditional use of "b" for the
denominator, which is not symmetric. In actuality, I would use
min(abs(a), abs(b)) for the denominator.]
In the case where we only specify absolute or relative tolerance, it is
obvious what to do: calculate the appropriate error, and if it is less
than the given tolerance, return True:
def approx_equal(a, b, allowed_absolute_error, allowed_relative_error):
# For simplicity, ignore NANs, INFs, and assume b != 0
actual_error = abs(a - b)
if allowed_absolute_error is None:
# Only perform a check on relative error.
return actual_error <= allowed_relative_error*abs(b)
elif allowed_relative_error is None:
# Only perform a check on absolute error.
return actual_error <= allowed_absolute_error
else:
# We have specified *both* abs and rel error.
How should we handle the third case? Two obvious ways come to mind:
require that *both* individual tests pass:
return (actual_error <= allowed_relative_error*abs(b)
and
actual_error <= allowed_absolute_error)
# equivalent to:
# actual_error <= max(allowed_relative_error*abs(b),
# allowed_absolute_error)
or require that *either* test pass:
return (actual_relative_error <= allowed_relative_error
or
actual_absolute_error <= allowed_absolute_error)
# equivalent to:
# actual_error <= min( ... )
But what numpy does is to add the tolerances together, that is, it uses
*twice* the average of them, equivalent to this:
allowed_error = (
allowed_absolute_error + allowed_relative_error*abs(b)
)
return actual_absolute_error <= allowed_error
This means that numpy will claim that two numbers are close even though
*both* the absolute and relative error tests fail:
py> numpy.allclose([1.2], [1.0], 0.0, 0.1) # Fails absolute error test.
False
py> numpy.allclose([1.2], [1.0], 0.1, 0.0) # Fails relative error test.
False
py> numpy.allclose([1.2], [1.0], 0.1, 0.1) # Passes!
True
I cannot think of a good justification for that. Either I am missing
something, or this goes to show that numpy can mess up even something as
simple and straightforward as an error calculation. If I'm right, that's
further evidence that getting this "right" and putting it in the
standard library is a good thing to do.
[...]
> Consider the problem of finding the (unique) 0 crossing (root) of a
> monotonic function f. One is looking for a value x* such that f(x*) is
> 'near' 0. (Someone claimed that 'nothing is close to zero'. This is
> nonsensical both in applied math and everyday life.)
It isn't nonsensical, it just needs to be understood in context of
relative errors. All non-zero numbers are infinitely far from zero in
terms of relative error.
> A standard
> approach is to compute successive approximations until one finds such a
> point. But unthinking application of such a method may not get one the
> desired result. The following are two examples with opposite problems.
[snip anecdote]
I didn't think that the well-known difficulties in root-finding has
anything to do with the usefulness of a standard way to compare numbers
for approximate equality.
--
Steven
More information about the Python-ideas
mailing list