[Python-Dev] Adventures with Decimal

Paul Moore p.f.moore at gmail.com
Sat May 21 14:12:50 CEST 2005


On 5/21/05, Raymond Hettinger <raymond.hettinger at verizon.net> wrote:
> A root difference is that I believe we have both a compliant
> implementation (using Context.create_decimal) and a practical context
> free extension in the form of the regular Decimal constructor.

Please forgive an intrusion by someone who has very little knowledge
of floating point pitfalls.

My mental model of Decimal is "pocket calculator arithmetic" (I
believe this was originally prompted by Tim, as I had previously been
unaware that calculators used decimal hardware). In that model, fixed
precision is the norm - it's the physical number of digits the box
displays. And setting the context is an extremely rare operation - it
models swapping to a different device (something I do do in real life,
when I have an 8-digit box and am working with numbers bigger than
that - but with Decimal, the model is a 28-digit box by default, and
that's big enough for me!)

Construction models typing a number in, and this is where the model
breaks down. On a calculator, you physically cannot enter a number
with more digits than the precision, so converting a string with
excess precision doesn't come into it. And yet, Decimal('...') is the
"obvious" constructor, and should do what people "expect".

In many ways, I could happily argue for an exception if the string has
too many digits. I could also argue for truncation (as that's what
many calculators actually do - ignore any excess typing). No
calculator rounds excess input, but I can accept it as what they might
well do if was physically possible. And of course, in a practical
sense, I'll be working with 28-digit precision, so I'll never hit the
situation in any case, and I don't care :-)

> A second difference is that you see harm in allowing any context free
> construction while I see greater harm from re-introducing representation
> error when that is what we were trying to fix in the first place.

The types of rounding errors (to use the naive term deliberately)
decimal suffer from are far more familiar to people because they use
calculators. With a calculator, I'm *used* to (1/3) * 3 not coming out
as exactly 1. And indeed we have

>>> (Decimal(1)/Decimal(3))*Decimal(3)
Decimal("0.9999999999999999999999999999")

Now try that with strings:

>>> (Decimal("1")/Decimal("3"))*Decimal("3")
Decimal("0.9999999999999999999999999999")
>>> (Decimal("1.0")/Decimal("3.0"))*Decimal("3.0")
Decimal("0.9999999999999999999999999999")

Nope, I don't see anything surprising.

After a bit more experimentation, I'm unable to make *anything*
surprise me, using either Decimal() or getcontext().create_decimal().
Of course, I've never bothered typing enough digits that I care about
(trailing zeroes don't count!) to trigger the rounding behaviour of
the constructor that matters here, but I don't ever epect to in real
life.

Apologies for the rambling discussion - it helped me as a non-expert
to understand what the issue is here. Having done so, I find that I am
unable to care. (Which is good, because I'm not the target audience
for the distinction :-))

So, to summarise, I can't see that a change would affect me at all. I
mildly favour Tim's position - because Raymond's seems to be based on
practicality for end users (where Tim's is based on convenience for
experts), and I can't see any practical effect on me to Tim's change.

OTOH, if end user impact were the driving force, I'd rather see
Decimal(string) raise an Inexact exception if the string would be
rounded:

>>> # Remember, my argument is that I'd never do the following in
practice, so this is
>>> # solely for a highly unusual edge case!
>>> decimal.getcontext().prec=5

>>> # This confuses me - it silently gives "the wrong" answer in my
mental model.
>>> Decimal("1.23456789") * 2
Decimal("2.4691")

>>> c = decimal.getcontext().copy()
>>> c.traps[decimal.Inexact] = True

>>> # This does what I expect - it tells me that I've done something wrong!
>>> c.create_decimal("1.23456789") * 2
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
  File "C:\Apps\Python24\lib\decimal.py", line 2291, in create_decimal
    return d._fix(self)
  File "C:\Apps\Python24\lib\decimal.py", line 1445, in _fix
    ans = ans._round(prec, context=context)
  File "C:\Apps\Python24\lib\decimal.py", line 1567, in _round
    context._raise_error(Inexact, 'Changed in rounding')
  File "C:\Apps\Python24\lib\decimal.py", line 2215, in _raise_error
    raise error, explanation
decimal.Inexact: Changed in rounding

I hope this helps,
Paul.


More information about the Python-Dev mailing list