# [Tutor] Re: If elif not working in comparison

C Smith smichr at hotmail.com
Tue Mar 29 12:06:59 CEST 2005

``` > gerardo arnaez wrote:
>
>> On Mon, 28 Mar 2005 09:27:11 -0500, orbitz <orbitz at
drorbitz.ath.cx>
>> wrote:
>>
>>> Floats are inherintly inprecise.  So if thigns arn't working like
you
>>> expect don't be surprised if 0.15, 0.12, and 0.1 are closer to the
same
>>> number than you think.
>>
>>
>>
>> Are you telling me that I cant expect 2 digit preceision?
>
>
> Not without using round. Have *NO* faith in floating points. This is
> especially true when you are creating the decimals via division and
the
> like.

What makes one nervous when looking at your tests with floating point
numbers is that calcuations that might lead up to one of the numbers in
your test might actually be a tiny bit higher or a tiny bit lower than
you expect.  *However*, I think the thing to keep in mind is that if
you are only a billionth or less away from the boundary, it really
doesn't matter which way you go in terms of dosage--you are at the
border.

Here is an example of two calculations that you think should get you to
2.1. One is actually just shy of it and the other just a bit past:

###
>>> 3*.7
2.0999999999999996
>>> 7*.3
2.1000000000000001
###

So if you have the following test to determine if it is time to
increase someone's dose of medication and the results are based on some
measurement being greater than 2.1, then...

###
>>> def test(x):
if x>2.1:
print 'increase dose'
else:
print 'decrease dose'

>>>
###

if you test the above computed values, you will get different results:

###
>>> dose1, dose2 = 3*.7, 7*.3
>>> test(dose1)
decrease dose
>>> test(dose2)
decrease dose
###

OR NOT! Why do they both come out to be the same even though one number
is bigger than 2.1 and the other not? Because 2.1 is not actually '2.1':

###
>>> 2.1
2.1000000000000001
###

The 7*.3 is actually "equal" to 2.1: they both are 2.1000000000000001.

On the one hand, we want the boundaries so we can say, "if you are past
this boundary you take the next sized dose"...but if you are so close
to the boundary that you can just as well say that you are right on it,
does it really matter which way you go? Other than bothering the
deterministic nature, are there other reasons to be concerned? As far
as economics, the '2,1' case will have people staying at a lower dose
longer (as would 2.2, 2.6 and 2.7 which all are a tiny bit bigger than
their mathematical values, just like 2.1).  Just the opposite will
happen at the 2.3, 2.4, 2.8, and 2.9 since these values are represented
as numbers smaller than their mathematical counterparts:

###
>>> 2.9
2.8999999999999999
###

If you have Python 2.4 you might want to check out the decimal type
that is now part of the language.  There is a description at

http://www.python.org/doc/2.4/whatsnew/node9.html

which starts with:

"Python has always supported floating-point (FP) numbers, based on the
underlying C double type, as a data type.  However, while most
programming languages provide a floating-point type, many people (even
programmers) are unaware that floating-point numbers don't represent
certain decimal fractions accurately.  The new Decimal type can
represent these fractions accurately, up to a user-specified precision
limit.  "

And it gives the following example:

Once you have Decimal instances, you can perform the usual mathematical
operations on them.  One limitation: exponentiation requires an integer
exponent:

>>> import decimal
>>> a = decimal.Decimal('35.72')
>>> b = decimal.Decimal('1.73')
>>> a+b
Decimal("37.45")
>>> a-b
Decimal("33.99")
>>> a*b
Decimal("61.7956")
>>> a/b
Decimal("20.64739884393063583815028902")

Another suggestion is to use the Round replacement that I wrote about
in response to the "Float precision untrustworthy~~~" thread. With it,
you don't have to worry about how large or small your numbers are when
you Round them (unlike with round()). You just decide on how many
"parts per x" you are interested in with your numbers.  e.g. if your
calculations are only precise to about 1 part per hundred then you
would round them to the 2nd (or maybe 3rd) digit and compare them.
With the separation of code and data as already suggested this would
look something like this:

###
def Round(x, n=0, sigfigs=False):
if x==0: return 0
if not sigfigs:
return round(x,n)
else:
#round to nth digit counting from first non-zero digit
# e.g. if n=3 and x = 0.002356 the result is 0.00236
return round(x,n-1-int(floor(log10(abs(x)))))
...
for cutoff, value in cutoffs:
if Round(x,3) < Round(cutoff,3):
return value
..
###

IMHO, I don't think that decimal is the way to go with such
calculations since calculations performed with limited precision
numbers shouldn't be treated as though they had infinite precision.
From basic error analysis of the calculated values above, one shouldn't
probably keep more than 3 digits in a/b result of
Decimal("20.64739884393063583815028902").  On the other hand, perhaps
one can keep all the digits until the end and then make the precision
decision--this gives the best of both worlds.

/c

```