On Thu, Jun 26, 2014 at 7:15 PM, Steven D'Aprano <st...@pearwood.info> wrote:
> Here's an error that *cannot* occur with binary floats: the average of
> two numbers x and y is not guaranteed to lie between x and y!
> py> from decimal import *
> py> getcontext().prec = 3
> py> x = Decimal('0.516')
> py> y = Decimal('0.518')
> py> (x + y) / 2
> Decimal('0.515')
> Ouch!

But what you're looking at is also a problem with intermediate
rounding, as the sum of .516 and .518 can't be represented in 3
digits. One rule of thumb that I learned back in my earliest coding
days was that your intermediate steps should have significantly more
precision than your end result; so if you want an end result with a
certain precision (say, 3 decimal digits), you should calculate with a
bit more. Of course, "a bit" is nearly impossible to define [1], but
if you're mostly adding and subtracting, or multiplying by smallish
constants, 1-2 extra digits' worth of precision is generally enough.
Or just give yourself lots of room, like using double-precision for
something like the above example. Compare this:

>>> from decimal import *
>>> getcontext().prec = 4
>>> x = Decimal('0.516')
>>> y = Decimal('0.519')
>>> avg = (x + y) / 2
>>> getcontext().prec = 3
>>> avg + 0
>>> (x + y) / 2

Doing the intermediate calculation with precision 3 exhibits the same
oddity Steven mentioned (only the other way around - result is too
high), but having a little extra room in the middle means the result
is as close to the correct answer as can be represented (0.517 would
be equally correct). With floating point on an 80x87, you can do this
with 80-bit FPU registers; I don't know of a way to do so with Python
floats, but (obviously) it's pretty easy with Decimal.


[1] Thank you, smart-aleck up the back, I am fully aware that "a bit"
is exactly one binary digit. That's not enough for a decimal float.
You've made your point, now shut up. :)

Reply via email to