[Python-Dev] Adventures with Decimal

Michael Chermside mcherm at mcherm.com
Mon May 23 14:10:45 CEST 2005


I'd like to respond to a few people, I'll start with Greg Ewing:

Greg writes:
> I don't see how it
> helps significantly to have just the very first
> step -- turning the input into numbers -- be
> exempt from this behaviour. If anything, people
> are going to be even more confused. "But it
> can obviously cope with 1.1000000000000000001,
> so why does it give the wrong answer when I add
> something to it?"

As I see it, there is a meaningful distinction between constructing
Decimal instances and performing arithmatic with them. I even think
this distinction is easy to explain to users, even beginners. See,
it's all about the program "doing what you tell it to".

If you type in this:
    x = decimal.Decimal("1.100000000000000000000000000003")
as a literal in your program, then you clearly intended for that
last decimal place to mean something. By contrast, if you were to
try passing a float to the Decimal constructor, it would raise an
exception expressly to protect users from "accidently" entering
something slightly off from what they meant.

On the other hand, in Python, if you type this:
    z = x + y
then what it does is completely dependent on the types of x and y.
In the case of Decimal objects, it performs a "perfect" arithmetic
operation then rounds to the current precision.

The simple explanation for users is "Context affects *operations*,
but not *instances*." This explains the behavior of operations, of
constructors, and also explains the fact that changing precision
doesn't affect the precision of existing instances. And it's only
6 words long.

> But I also found it interesting that, while the spec
> requires the existence of a context for each operation,
> it apparently *doesn't* mandate that it must be kept
> in a global variable, which is the part that makes me
> uncomfortable.
>
> Was there any debate about this choice when the Decimal
> module was being designed?

It shouldn't make you uncomfortable. Storing something in a global
variable is a BAD idea... it is just begging for threads to mess
each other up. The decimal module avoided this by storing a SEPARATE
context for each thread, so different threads won't interfere with
each other. And there *is* a means for easy access to the context
objects... decimal.getcontext().

Yes, it was debated, and the debate led to changing from a global
variable to the existing arrangement.

------
As long as I'm writing, let me echo Nick Coghlan's point:
> The fact that the BDFL (and others, me included) were at least temporarily
> confused by the ability to pass a context in to the constructor suggests there
> is an interface problem here.
>
> The thing that appears to be confusing is that you *can* pass a context in to
> the Decimal constructor, but that context is then almost completely ignored.

Yeah... I agree. If you provide a Context, it should be used. I favor changing
the behavior of the constructor as follows:

     def Decimal(data, context=None):
         result = Existing_Version_Of_Decimal(data)
         if context is None:
             result = +result
         return result

In other words, make FULL use of the context in the constructor if a context
is provided, but make NO use of the thread context when no context is
provided.

------
One final point... Thanks to Mike Cowlishaw for chiming in with a detailed
and well-considered explanation of his thoughts on the matter.

-- Michael Chermside



More information about the Python-Dev mailing list