[Tutor] Python 2.5.4 - error in rounding

Steven D'Aprano steve at pearwood.info
Sat May 22 18:58:57 CEST 2010


On Sun, 23 May 2010 12:19:07 am Wayne Werner wrote:
> On Sat, May 22, 2010 at 7:32 AM, Steven D'Aprano 
<steve at pearwood.info>wrote:
> > Why do people keep recommending Decimal? Decimals suffer from the
> > exact same issues as floats,
>
> This is exactly incorrect! The Decimal operator offers /exact/
> decimal point operations.

Decimal is only exact for fractions which can be represented by a finite 
sum of powers-of-ten, like 0.1, just like floats can only represent 
fractions exactly if they can be represented by a finite sum of 
powers-of-two, like 0.5.

Not only did I demonstrate an example of rounding error using Decimal in 
my post, but you then repeated that rounding error and then had the 
audacity to claim that it was "exact":


> For an example about the exactness of Decimal v Float:
> >>> d = Decimal(1)/Decimal(3)
> >>> d
>
> Decimal('0.3333333333333333333333333333')
>
> >>> d*Decimal(3)
>
> Decimal('0.9999999999999999999999999999')

Does that look like exactly one to you? I don't know what they taught 
you, but when I was in school, I learned that one was exactly 1, not 
0.9 or 0.999 or even 0.9999999999999999999999999999.

But don't believe five hundred years of mathematics, do the test 
yourself:

>>> d*Decimal(3) == 1
False

We can calculate the exact error:

>>> d = Decimal(1)/Decimal(3)
>>> 1 - d*Decimal(3) == 0
False
>>> 1 - d*Decimal(3)
Decimal('1E-28')

Small, but not zero. And adding more precision isn't the answer: it will 
make the error smaller, true enough, but not zero:

>>> decimal.getcontext().prec = 100
>>> d = Decimal(1)/Decimal(3)
>>> d*Decimal(3) == 1
False
>>> 1 - d*Decimal(3)
Decimal('1E-100')

To get that error to zero exactly, you need an infinite precision.


> They implement non-hardware operations to 
> preserve exactness. For more information on exactly what and how the
> decimal module does what it does, see the following:
>
> http://docs.python.org/library/decimal.html

I know what the decimal module does. It is capable of representing 
*decimal* numbers exactly, but not all fractions are exact decimal 
numbers, just as not all fractions are exact binary numbers. For exact 
fractions, you need the fractions module.


>  plus they are slower.
>
>
> Because if memory serves correctly the Python implementation uses
> serial arithmetic, rather than the hardware implementation of
> floating point calculations.

I don't understand what you mean by serial arithmetic, but the reason 
the decimal module is slow is that it is currently written in pure 
Python. A C version would be faster (but still slower than the hardware 
implementation of float calculations).


> Please stop propagating myths about the Decimal module.

I'm not. You are misrepresenting Decimal as a panacea for all rounding 
issues, which it is not.


> >>> d = 1/3.0
> >>> d*3
> 1.0

Curiously, floats perform that specific calculation better than Decimal. 
That shows that sometimes you can have *too much* precision for a 
calculation. float rounds off the answer after just 17 decimal places 
(by memory), giving exactly 1, while Decimal (by default) rounds to 28 
places, leading to an error.

Of course, there are other calculations where Decimal is more accurate:

>>> sum(0.1 for i in range(10)) == 1
False
>>> sum(Decimal('0.1') for i in range(10)) == 1
True

This is only to be expected, because 0.1 is an exact power of ten, but 
not an exact power of two.



-- 
Steven D'Aprano


More information about the Tutor mailing list