(original) (raw)
I've been thinking
about a function that was recently proposed at python-dev named 'areclose'.
It is a function that is meant to tell whether two (or possible more) numbers
are close to each other. It is a function similar to one that exists in
Numeric. One such implementation is
def
areclose(x,y,abs\_tol=1e-8,rel\_tol=1e-5):
diff =
abs(x-y)
return diff
<= ans\_tol or diff <= rel\_tol\*max(abs(x),abs(y))
(This is the form given by
Scott Daniels on python-dev.)
Anyway, one of
the rationales for including such a function was:
When teaching some programming to total newbies, a common frustration
is how to explain why a==b is False when a and b are floats computed
by different routes which \`\`should'' give the same results (if
arithmetic had infinite precision). Decimals can help, but another
approach I've found useful is embodied in Numeric.allclose(a,b) --
which returns True if all items of the arrays are \`\`close'' (equal to
within certain absolute and relative tolerances)
The problem with the above
function, however, is that it \*itself\* has a comparison between floats and
it will give undesired result for something like the following
test:
###
>>> print areclose(2,
2.1, .1, 0) #see if 2 and 2.1 are within 0.1 of each other
False
>>>
###
Here is an alternative that
might be a nice companion to the repr() and round() functions: nice(). It is a
combination of Tim Peter's delightful 'case closed' presentation in the thread,
"Rounding to n significant digits?" \[1\] and the hidden magic of "prints"
simplification of floating point numbers when being asked to show them.
It's default behavior is to
return a number in the form that the number would have when being printed. An
optional argument, however, allows the user to specify the number of digits to
round the number to as counted from the most significant digit. (An alternative
name, then, could be 'lround' but I think there is less baggage for the new user
to think about if the name is something like nice()--a function that makes the
floating point numbers "play nice." And I also think the name...sounds
nice.)
Here it is in
action:
###
>>>
3\*1.1==3.3
False
>>> nice(3\*1.1)==nice(3.3)
True
>>> x=3.21/0.65; print x
4.93846153846
False
>>> nice(3\*1.1)==nice(3.3)
True
>>> x=3.21/0.65; print x
4.93846153846
>>> print
nice(x,2)
4.9
>>> x=x\*1e5; print nice(x,2)
490000.0
###
>>> x=x\*1e5; print nice(x,2)
490000.0
###
Here's the function:
###
def
nice(x,leadingDigits=0):
"""Return x either as 'print' would show it (the default) or rounded to the
specified digit as counted from the leftmost non-zero digit of the number,
"""Return x either as 'print' would show it (the default) or rounded to the
specified digit as counted from the leftmost non-zero digit of the number,
e.g. nice(0.00326,2)
\--> 0.0033"""
assert leadingDigits>=0
if leadingDigits==0:
return float(str(x)) #just give it back like 'print' would give it
leadingDigits=int(leadingDigits)
return float('%.\*e' % (leadingDigits,x)) #give it back as rounded by the %e format
assert leadingDigits>=0
if leadingDigits==0:
return float(str(x)) #just give it back like 'print' would give it
leadingDigits=int(leadingDigits)
return float('%.\*e' % (leadingDigits,x)) #give it back as rounded by the %e format
###
Might something like this be useful? For new
users, no arguments are needed other than x and floating points suddenly seem to
behave in tests made using nice() values. It's also useful for those computing
who want to show a physically meaningful value that has been rounded to the
appropriate digit as counted from the most significant digit rather than from
the decimal point.
Some time back I had worked on the
significant digit problem and had several math calls to figure out what the
exponent was. The beauty of Tim's solution is that you just use built in string
formatting to do the work. Nice.
/c