[Tutor] range function and floats?
Steven D'Aprano
steve at pearwood.info
Wed Jan 5 23:59:14 CET 2011
Wayne Werner wrote:
> On Wed, Jan 5, 2011 at 10:43 AM, Steven D'Aprano <steve at pearwood.info>wrote:
>
>> Wayne Werner wrote:
>>
>> The decimal module allows you to get rid of those pesky floating point
>>> errors. See http://docs.python.org/library/decimal.html for more info.
>>>
>> That's a myth. Decimal suffers from the same floating point issues as
>> binary floats. It's quite easy to demonstrate the same sort of rounding
>> errors with Decimal as for float:
>>
>>>>> from decimal import Decimal as D
>>>>> x = D(1)/D(3)
>>>>> 3*x == 1
>> False
>>
>
> I should have clarified - when I say pesky floating point errors I mean
> errors in precision that you naturally would not expect.
Say what?
But that's the whole point -- you *must* expect them, because Decimal
floating point numbers suffer the *exact* same issues with rounding and
finite precision as binary floats. It may affect different specific
numbers, but the issues are exactly the same. Just because Decimal uses
base 10 doesn't make it immune to the same issues of rounding and
precision as base 2 floats.
> 1/3 == .333 (repeating forever(?)). But on a computer, you're limited to a
> specific precision point (sort of), and the moment you truncate
> .333(repeating) to *any* finite points of precision you no longer have the
> result of the mathematical operation 1/3. Yes, 1/3*3 == 1, but the error in
> the Decimal module is *only* in division. It might be useful to define a
I'm surprised you can make such an obviously wrong claim -- you even
discuss errors in addition further down. How can you do that and still
say that errors only occur with division???
Decimal includes a significant amount of code to detect when the result
of some operation is inexact, and to signal the user when it occurs.
This does *not* only happen in division:
>>> import decimal
>>> decimal.getcontext().traps[decimal.Inexact] = 1
>>> a = decimal.Decimal('1e-30')
>>> b = decimal.Decimal('11.1')
>>> a+b
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/local/lib/python3.1/decimal.py", line 1178, in __add__
ans = ans._fix(context)
File "/usr/local/lib/python3.1/decimal.py", line 1653, in _fix
context._raise_error(Inexact)
File "/usr/local/lib/python3.1/decimal.py", line 3812, in _raise_error
raise error(explanation)
decimal.Inexact: None
> "repeating" flag in the Decimal module for the results of such operations as
> 1/3, which would get rid of the error in truncation. But this is a
You haven't thought that through very well. How do you deal with square
roots and other fractional powers without truncation errors?
What happens when you operate on two numbers with this repeating flag --
is the Decimal module supposed to include a full-blown algebra system to
generate exact answers to such things as this?
x = 1/Decimal(3) # 0.333 repeating
y = 1/Decimal(27) # 0.037037 repeating
x - y/10**9 # 0.333333333296296296 repeating
How do you deal with numbers like pi or e without truncation?
No, a repeating flag would just add unnecessary complexity for no real
benefit. If you want to track fractions exactly, use the fractions module.
> fundamentally different error from the standard floating point errors.
But they aren't fundamentally different! That's the point! They are the
same errors. They effect different numbers, because float is base 2 and
Decimal is base 10, but they have the same fundamental cause.
> In [106]: 0.9
> Out[106]: 0.90000000000000002
>
> These are two different numbers, mathematically. If I say x = 0.9, I
> naturally assume that 0.9 is the value in x, not 0.90000000000000002. Of
Yes, and the exact same issue can occur in Decimal. It just affects
different numbers. 0.9 is exact in base 10, but can't be represented
exactly in base 2. Fortunately, the opposite is not the case: any finite
number of binary bits is equivalent to a finite number of decimal
digits, so unless you overflow the number of digits, you won't get an
equivalent error for binary->decimal conversion. But there are numbers
which can't be represented exactly in base 10 at any finite precision.
1/3 is one of them, so is 5/17 and an infinite number of other examples.
You can even find examples of numbers which are exact in base 2 but
inexact in Decimal at whatever finite precision you choose.
>>> 2**120
1329227995784915872903807060280344576
>>> int( 1/( 1/Decimal(2**120) ) )
1329227995784915872903807060000000000
Choose more digits for Decimal, and 2**120 can be stored exactly, but
other, larger, numbers can't.
> course it's all in the implementation of floating points, and the fact that
> Python evaluates 0.9 in a floating point context which results in the stored
> value, but that ignores the fact that *naturally* one does not expect this.
> And anyone who's been through 2nd or 3rd grade or whenever they teach about
> equality would expect that this would evaluate to False.
Floating point numbers are not the same as the real numbers you learn
about in school. It doesn't matter whether you use binary floats or
decimal floats or base-19 floats.
> In [112]: 0.90000000000000002 == 0.9
> Out[112]: True
>
> You don't get such silliness with the Decimal module:
>
> In [125]: D('0.90000000000000002') == D('0.9')
> Out[125]: False
Naturally Decimal is good at representing numbers which can be written
exactly in decimal. That's what it was designed for :)
> Decimal DOES get rid of floating point errors, except in the case of
> repeating (or prohibitively large precision values)
In other words, decimal gets rid of floating point errors, except for
floating point errors. That's exactly what floating point errors are:
numbers which can't be represented exactly in the given number of bits
or digits.
Regardless of base, you must expect floating point calculations to
overflow, underflow, or just generally experience rounding errors.
[...]
> If you reset the precision to an incredibly large number:
> decimal.getcontext().prec = 1000
Yes, one advantage of Decimal is that it gives you control of how many
digits the numbers can store. This is a good, but expensive, feature.
Nevertheless, it doesn't change the existence of rounding errors. No
matter what precision you choose, I can ALWAYS give you numbers that
will overflow or underflow that precision.
>
> In [131]: x = D(10)**30
>
> In [132]: x
> Out[132]: Decimal('1000000000000000000000000000000')
>
> In [133]: x + 100 == x
> Out[133]: False
>
> Voila, the error has vanished!
But only for that specific value. This is no different from the binary
world, going from single precision floats to double to quad precision.
At each stage you can represent more numbers exactly, because you have
more bits to store them in, but there are always numbers that require
more. Decimal is no different.
>> So it simply isn't correct to suggest that Decimal doesn't suffer from
>> rounding error.
>
>
> I never said rounding errors - I said "pesky floating point errors". When
Which ARE rounding errors. They're *all* rounding errors, caused by the
same fundamental issue -- the impossibility of representing some
specific exact number in the finite number of bits, or digits, available.
Only the specific numbers change, not the existence of the errors.
--
Steven
More information about the Tutor
mailing list