Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

prePEP: Decimal data type

6 views
Skip to first unread message

Batista, Facundo

unread,
Oct 31, 2003, 1:36:38 PM10/31/03
to Python-List (E-mail)
Here I send it.

Suggestions and all kinds of recomendations are more than welcomed.

If it all goes ok, it'll be a PEP when I finish writing/modifying the code.

Thank you.

. Facundo


------------------------------------------------------------------------

PEP: XXXX
Title: Decimal data type
Version: $Revision: 0.1 $
Last-Modified: $Date: 2003/10/31 15:25:00 $
Author: Facundo Batista <fbat...@unifon.com.ar>
Status: Draft
Type: Standards Track
Content-Type: text/x-rst
Created: 17-Oct-2003
Python-Version: 2.3.3


Abstract
========

The idea is to have a Decimal data type, for every use where decimals are
needed but floating point is too inexact.

The Decimal data type should support the Python standard functions and
operations and must comply the decimal arithmetic ANSI standard X3.274-1996.


Rationale
=========

I must separate the requeriments in two sections. The first is to comply
with the ANSI standard. All the needings for this are specified in the
Mike Cowlishaw's work at http://www2.hursley.ibm.com/decimal/. Cowlishaw's
also provided a **lot** of test cases. The second section of requeriments
(standard Python functions support, usability, etc) are detailed in the
`Requirements`_ section.

Here I'll include all the decisions made and why, and all the subjects still
being discussed. The requirements will be numbered, to simplify discussion
on each point.

This work is based on code and test functions written by Eric Price, Aahz
and
Tim Peters. Actually I'll work on the Decimal.py code in the sandbox (at
python/nondist/sandbox/decimal in SourceForge). Some of the explanations of
this PEP are taken from the Cowlishaw's work.


Items In Discussion
-------------------

When in a case like ``Decimal op otherType`` (see point 12 in Requirements_
for details), what should happen?

if otherType is an int or long:

a. an exception is raised
b. otherType is converted to Decimal
c. Decimal is converted to int or long (with ``int()`` or
``long()``)

if otherType is a float:

d. an exception is raised
e. otherType is converted to Decimal (rounding? see next item in
discussion)
f. Decimal is converted to float (with ``float()``)

if otherType is a string:

g. an exception is raised
h. otherType is converted to Decimal
i. Decimal is converted to string (bizarre, huh?)


When passing floating point to the constructor, what should happen?

j. ``Decimal(1.1) == Decimal('1.1')``
k. ``Decimal(1.1) ==
Decimal('110000000000000008881784197001252...e-51')``


Requirements
============

1. The syntax should be ``Decimal(value)``.

2. The value could be of the type:

- another Decimal
- int or long
- float
- string

3. To exist a Context. The context represents the user-selectable
parameters
and rules which govern the results of arithmetic operations. In the
context the user defines:

- what will happen with the exceptional conditions.
- what precision will be used
- what rounding method will be used

4. The Context must be omnipresent, meaning that changes to it affects all
the current and future Decimal instances.

5. The exceptional conditions should be grouped into signals, which could be
controlled individually. The context should contain a flag and a
trap-enabler for each signal. The signals should be: clamped,
division-by-zero, inexact, invalid-operation, overflow, rounded,
subnormal
and underflow.

6. For each of the signals, the corresponding flag should be set to 1 when
the signal occurs. It is only reset to 0 by explicit user action.

7. For each of the signals, the corresponding trap-enabler will indicate
which action is to be taken when the signal occurs. If 0, a defined
result should be supplied, and execution should continue. If 1, the
execution of the operation should end and an exception should be raised.

8. The precision (maximum number of significant digits that can result from
an arithmetic operation) must be positive (greater than 0).

9. To have different kinds of rounding; you can choose the algorithm through
context:

- ``round-down``: (Round toward 0, truncate) The discarded digits are
ignored; the result is unchanged::

1.123 --> 1.12
1.128 --> 1.12
1.125 --> 1.12
1.135 --> 1.13

- ``round-half-up``: If the discarded digits represent greater than
or
equal to half (0.5) then the result should be incremented by 1
(rounded up); otherwise the discarded digits are ignored::

1.123 --> 1.12
1.128 --> 1.13
1.125 --> 1.13
1.135 --> 1.14

- ``round-half-even``: If the discarded digits represent greater than
half (0.5) then the result coefficient should be incremented by 1
(rounded up); if they represent less than half, then the result is
not adjusted (that is, the discarded digits are ignored); otherwise
the result is unaltered if its rightmost digit is even, or
incremented by 1 (rounded up) if its rightmost digit is odd (to
make
an even digit)::

1.123 --> 1.12
1.128 --> 1.13
1.125 --> 1.12
1.135 --> 1.14

- ``round-ceiling``: If all of the discarded digits are zero or if
the
sign is negative the result is unchanged; otherwise, the result
should be incremented by 1 (rounded up)::

1.123 --> 1.13
1.128 --> 1.13
-1.123 --> -1.12
-1.128 --> -1.12

- ``round-floor``: If all of the discarded digits are zero or if the
sign is positive the result is unchanged; otherwise, the absolute
value of the result should be incremented by 1::

1.123 --> 1.12
1.128 --> 1.12
-1.123 --> -1.13
-1.128 --> -1.13

- ``round-half-down``: If the discarded digits represent greater than
half (0.5) then the result should be incremented by 1 (rounded up);
otherwise the discarded digits are ignored::

1.123 --> 1.12
1.128 --> 1.13
1.125 --> 1.12
1.135 --> 1.13

- ``round-up``: (Round away from 0) If all of the discarded digits
are
zero the result is unchanged. Otherwise, the result should be
incremented by 1 (rounded up)::

1.123 --> 1.13
1.128 --> 1.13
1.125 --> 1.13
1.135 --> 1.14

10. Strings with floats in engineering notation will be supported.

11. Calling repr() should do round trip, meaning that::

m = Decimal(...)
m == eval(repr(m))

12. To support the basic aritmetic (``+, -, *, /, //, **, %, divmod``) and
comparison (``==, !=, <, >, <=, >=, cmp``) operators in the following
cases:

- Decimal op Decimal
- Decimal op otherType
- otherType op Decimal
- Decimal op= Decimal
- Decimal op= otherType

Check `Items In Discussion`_ to see what types could OtherType be, and
what happens in each case.

13. To support unary operators (``-, +, abs``).

14. To support the built-in methods:

- min, max
- float, int, long
- str, repr
- hash
- copy, deepcopy
- bool (0 is false, otherwise true)

15. To be immutable.


Reference Implementation
========================

To be included later:

- code
- test code
- documentation


Copyright
=========

This document has been placed in the public domain.

Emile van Sebille

unread,
Oct 31, 2003, 5:04:41 PM10/31/03
to

"Batista, Facundo" <FBat...@uniFON.com.ar> wrote in message
news:mailman.295.1067625...@python.org...

> Here I send it.
>
> Suggestions and all kinds of recomendations are more than welcomed.
>

[snip]

> Items In Discussion
> -------------------
>
> When in a case like ``Decimal op otherType`` (see point 12 in
Requirements_
> for details), what should happen?
>
> if otherType is an int or long:
>
> a. an exception is raised
> b. otherType is converted to Decimal
> c. Decimal is converted to int or long (with ``int()`` or
> ``long()``)
>

Wouldn't you want the result to return a Decimal? eg:

price = Decimal(3.15)
qty = 3
extension = qty * price

extension should now be a Decimal instance.

In that case, it looks like (b.) wins by default, as (a.) is clearly not
what you'd want; and (c.) yields potential results that give rise to needing
Decimal in the first place. I'd expect anything functionally less than the
above pseudo-snippet to be cumbersome to use.


> if otherType is a float:
>
> d. an exception is raised
> e. otherType is converted to Decimal (rounding? see next item in
> discussion)
> f. Decimal is converted to float (with ``float()``)
>

retail = Decimal(3.15)
discount = 35
wholesale = retail * (100-discount)/100

Here again, if the choices are limited to one of d|e|f then e seems the
best.

> if otherType is a string:
>
> g. an exception is raised
> h. otherType is converted to Decimal
> i. Decimal is converted to string (bizarre, huh?)
>

(g.) - I can't think of a use case where you'd want a known Decimal to work
with a string.


>
> When passing floating point to the constructor, what should happen?
>
> j. ``Decimal(1.1) == Decimal('1.1')``
> k. ``Decimal(1.1) ==
> Decimal('110000000000000008881784197001252...e-51')``
>

Well, persistence will bring in a whole set of problems. (j.) is, of
course, what I'd want.

[snip]

> 3. To exist a Context. The context represents the user-selectable
> parameters
> and rules which govern the results of arithmetic operations. In the
> context the user defines:
>
> - what will happen with the exceptional conditions.
> - what precision will be used
> - what rounding method will be used
>
> 4. The Context must be omnipresent, meaning that changes to it affects all
> the current and future Decimal instances.

Does this imply then that there is unlimited precision being maintained
under the covers?


Emile van Sebille
em...@fenx.com

Mel Wilson

unread,
Nov 1, 2003, 8:18:33 AM11/1/03
to
In article <mailman.295.1067625...@python.org>,

"Batista, Facundo" <FBat...@uniFON.com.ar> wrote:
>When passing floating point to the constructor, what should happen?
>
> j. ``Decimal(1.1) == Decimal('1.1')``
> k. ``Decimal(1.1) ==
>DecimFl('110000000000000008881784197001252...e-51')``

I've been playing with an implementation, and I'm trying out

def __init__ (value=0, exponent=0, places=None):
...

Under the hood there's a decimal-floating-point implementation,
using long for the radix and integer (or long) for the exponent.

To get 11:
d = Decimal (11)

To get 1.1 exactly:
d = Decimal (11, -1)

To get 1.1 from a float:
f = 1.1
d = Decimal (f, 0, 1) # take float value to 1 decimal place

d = Decimal (1.1) # gets `places` from pre-set context


Regards. Mel.

John Roth

unread,
Nov 1, 2003, 10:15:43 AM11/1/03
to
Lots of comments in line. See especially the comment
about *NOT* wanting limited precision, as envisioned
in the referenced decimal *floating point* standards.

John Roth


"Batista, Facundo" <FBat...@uniFON.com.ar> wrote in message
news:mailman.295.1067625...@python.org...

> Here I send it.
>
> Suggestions and all kinds of recomendations are more than welcomed.
>
> If it all goes ok, it'll be a PEP when I finish writing/modifying the
code.
>
> Thank you.
>
> . Facundo
>
>
> ------------------------------------------------------------------------
>
> PEP: XXXX
> Title: Decimal data type
> Version: $Revision: 0.1 $
> Last-Modified: $Date: 2003/10/31 15:25:00 $
> Author: Facundo Batista <fbat...@unifon.com.ar>
> Status: Draft
> Type: Standards Track
> Content-Type: text/x-rst
> Created: 17-Oct-2003
> Python-Version: 2.3.3
>
>
> Abstract
> ========
>
> The idea is to have a Decimal data type, for every use where decimals are
> needed but floating point is too inexact.
>
> The Decimal data type should support the Python standard functions and
> operations and must comply the decimal arithmetic ANSI standard
X3.274-1996.

Why is ANSI 274 significant? The reason I ask this is that this is
a ***floating point*** standard, and I don't think that we particularly
care for decimal floating point.

Floating point presumes limited precision. In other words, if the actual
number (exclusive of the location of the decimal point) gets too large,
the least significant part is ... thrown away. I don't want that.

Since we've got infinite precision integer arithmetic, going to
limited precision decimal arithmetic is, IMNSHO, a step backwards.

The "other type" should be handled in the same way the decimal()
constructor would handle it.


> When passing floating point to the constructor, what should happen?
>
> j. ``Decimal(1.1) == Decimal('1.1')``
> k. ``Decimal(1.1) ==
> Decimal('110000000000000008881784197001252...e-51')``

Clearly, j is the correct answer. It's not all that hard to do, either.

>
>
> Requirements
> ============
>
> 1. The syntax should be ``Decimal(value)``.

Lower case: "decimal(value, [scale factor])"

> 2. The value could be of the type:
>
> - another Decimal
> - int or long
> - float
> - string

OK.

> 3. To exist a Context. The context represents the user-selectable
> parameters
> and rules which govern the results of arithmetic operations. In the
> context the user defines:
>
> - what will happen with the exceptional conditions.
> - what precision will be used
> - what rounding method will be used

See my general comment above with respect to precision. We should
not implement limited precision. Likewise, there is no reason to have
exceptional conditions in the sense of the IEEE floating point standard.

> 4. The Context must be omnipresent, meaning that changes to it affects all
> the current and future Decimal instances.

No. The context should be selectable for the particular usage. That is,
it should be possible to have several different contexts in play at one
time in an application.

>
> 5. The exceptional conditions should be grouped into signals, which could
be
> controlled individually. The context should contain a flag and a
> trap-enabler for each signal. The signals should be: clamped,
> division-by-zero, inexact, invalid-operation, overflow, rounded,
> subnormal
> and underflow.

See my general comment on limited precision arithmetic. This eliminates
all of the possible exceptional conditions except division-by-zero and
inexact (which is only possible for division and sqrt.) Division by zero
should always be handled by an exception; square root needs its own
specification.

> 6. For each of the signals, the corresponding flag should be set to 1 when
> the signal occurs. It is only reset to 0 by explicit user action.

Since I've just eliminated the signals, this vanishes.

>
> 7. For each of the signals, the corresponding trap-enabler will indicate
> which action is to be taken when the signal occurs. If 0, a defined
> result should be supplied, and execution should continue. If 1, the
> execution of the operation should end and an exception should be
raised.

Likewise.

> 8. The precision (maximum number of significant digits that can result
from
> an arithmetic operation) must be positive (greater than 0).

See my general comment on the undesirability of limited precision
arithmetic.

I think this is simply too much. I'd rather have a round() method that
takes a *small* number of standard options, and otherwise takes a
function to do the rounding.

>
> 10. Strings with floats in engineering notation will be supported.

OK.

>
> 11. Calling repr() should do round trip, meaning that::
>
> m = Decimal(...)
> m == eval(repr(m))

OK.


> 12. To support the basic aritmetic (``+, -, *, /, //, **, %, divmod``) and
> comparison (``==, !=, <, >, <=, >=, cmp``) operators in the following
> cases:
>
> - Decimal op Decimal
> - Decimal op otherType
> - otherType op Decimal
> - Decimal op= Decimal
> - Decimal op= otherType
>
> Check `Items In Discussion`_ to see what types could OtherType be, and
> what happens in each case.

> 13. To support unary operators (``-, +, abs``).

OK.


>
> 14. To support the built-in methods:
>
> - min, max
> - float, int, long
> - str, repr
> - hash
> - copy, deepcopy
> - bool (0 is false, otherwise true)

OK, although I note that sqrt() isn't included, which makes
the discussion of "inexact" earlier moot.
>
> 15. To be immutable.

OK.

Alex Martelli

unread,
Nov 1, 2003, 10:50:02 AM11/1/03
to
John Roth wrote:

> Lots of comments in line. See especially the comment
> about *NOT* wanting limited precision, as envisioned
> in the referenced decimal *floating point* standards.

...


> Since we've got infinite precision integer arithmetic, going to
> limited precision decimal arithmetic is, IMNSHO, a step backwards.

There may be a niche for a Rational data type, but in MHO it cannot take the
place of a limited-precision-decimal (fixed or float). I suggest you think
of a separate PEP to propose Rational (check the existing and rejected ones
first, there may be some that are relevant) rather than attacking this one.

I think Guido will never accept rationals becoming as widespread as they
were in ABC (the default noninteger type) and is on record as saying that.
The performance implications of the fact that summing two rationals (which
take O(M) and O(N) space respectively) gives a rational which takes O(M+N)
memory space is just too troublesome. There are excellent Rational
implementations in both pure Python and as extensions (e.g., gmpy), but
they'll always be a "niche market" IMHO. Probably worth PEPping, not worth
doing without Decimal -- which is the right way to represent sums of money,
a truly major use case in the real world.

(Facundo, I think you should include these considerations in the PEP to
explain why you're NOT going for rationals.)


>> if otherType is a string:
>>
>> g. an exception is raised
>> h. otherType is converted to Decimal
>> i. Decimal is converted to string (bizarre, huh?)
>
> The "other type" should be handled in the same way the decimal()
> constructor would handle it.

I think this total breach with Python tradition would be a terrible mistake.

23+"43" is NOT handled in the same way as 23+int("45"), and a VERY
good thing that is too. It's a completely different thing for a user to
EXPLICITLY indicate they want construction (conversion) and to just happen
to sum two objects one of which by mistake could be a string.

(Facundo, perhaps it's worth it for the PEP to point this out explicitly,
too; part of a PEP's purpose is to record the highlights of the discussion
and design choices that had to be made).


Alex

Paul Moore

unread,
Nov 1, 2003, 10:55:43 AM11/1/03
to
"John Roth" <newsg...@jhrothjr.com> writes:

>> The idea is to have a Decimal data type, for every use where decimals are
>> needed but floating point is too inexact.
>>
>> The Decimal data type should support the Python standard functions and
>> operations and must comply the decimal arithmetic ANSI standard
>> X3.274-1996.
>
> Why is ANSI 274 significant? The reason I ask this is that this is
> a ***floating point*** standard, and I don't think that we particularly
> care for decimal floating point.

To be honest, I have little if any practical experience with numeric
representation issues, but it seems sensible to me to implement a
pre-existing, and presumably well thought out, standard, rather than
inventing something ad-hoc.

Of course, if the need is for something other than what ANSI 274
standardises, which seems to be what you are implying, then fair
enough. But what do you require? Infinite-precision decimal
arithmetic? If so, can you explain how you'd handle something like
1/3?

(I'm not being deliberately awkward here - my impression is that
representation issues are *hard*, and there are a lot of traps you can
fall into by oversimplifying. That's why I prefer the idea of a
pre-existing standard: it implies that *someone* has thought about the
hard stuff).

> Floating point presumes limited precision. In other words, if the actual
> number (exclusive of the location of the decimal point) gets too large,
> the least significant part is ... thrown away. I don't want that.
>
> Since we've got infinite precision integer arithmetic, going to
> limited precision decimal arithmetic is, IMNSHO, a step backwards.

Even infinite precision integers throw away information, in some
sense. Witness:

>>> 1L//3L
0L

>> When passing floating point to the constructor, what should happen?
>>
>> j. ``Decimal(1.1) == Decimal('1.1')``
>> k. ``Decimal(1.1) ==
>> Decimal('110000000000000008881784197001252...e-51')``
>
> Clearly, j is the correct answer. It's not all that hard to do, either.

No way. Consider:

>>> 1.1
1.1000000000000001
>>> 1.1==1.1000000000000001
True

So what should Decimal(1.1000000000000001) evaluate to? It can't be
Decimal('1.1'), as that contradicts your statement that j "clearly"
applies. But it *also* can't be Decimal('1.1000000000000001'), as then
we have the *same number* converting to two *different* Decimal
values.

As I say, it's hard.

I'd probably support Decimal(float) giving an exception, on the basis
that if you're doing this, you probably don't know what you're getting
into :-) Having a special method, say Decimal.round_float(f, digits),
is probably OK, though...

Paul.
--
This signature intentionally left blank

John Roth

unread,
Nov 1, 2003, 11:25:23 AM11/1/03
to

"Alex Martelli" <ale...@yahoo.com> wrote in message
news:KCQob.78275$e5.29...@news1.tin.it...

> John Roth wrote:
>
> > Lots of comments in line. See especially the comment
> > about *NOT* wanting limited precision, as envisioned
> > in the referenced decimal *floating point* standards.
> ...
> > Since we've got infinite precision integer arithmetic, going to
> > limited precision decimal arithmetic is, IMNSHO, a step backwards.
>
> There may be a niche for a Rational data type, but in MHO it cannot take
the
> place of a limited-precision-decimal (fixed or float). I suggest you
think
> of a separate PEP to propose Rational (check the existing and rejected
ones
> first, there may be some that are relevant) rather than attacking this
one.

Alex, where did I suggest that I wanted a rational data type? Please
tell me one place in my response where I said that. Please?

For the record. I'm not suggesting a rational data type. I'm quite
well aware of the arguements pro and con, and Guido's position.
Please read what I said without your preconceptions.

The only place where you can get into trouble is with division
and equivalent operations. That's the one place where you actually
need to specify the result number of decimal places and the rounding
policy. Every other operation has a well defined result that won't
ever lead to repeating decimal representations, etc.

My basic suggestion for that is to replace the division operators
with a div() function that lets you specify the number of places
and the rounding policy.

John Roth


>
>
> Alex
>


John Roth

unread,
Nov 1, 2003, 11:40:36 AM11/1/03
to

"Paul Moore" <pf_m...@yahoo.co.uk> wrote in message
news:3cd884...@yahoo.co.uk...

> "John Roth" <newsg...@jhrothjr.com> writes:
>
> >> The idea is to have a Decimal data type, for every use where decimals
are
> >> needed but floating point is too inexact.
> >>
> >> The Decimal data type should support the Python standard functions and
> >> operations and must comply the decimal arithmetic ANSI standard
> >> X3.274-1996.
> >
> > Why is ANSI 274 significant? The reason I ask this is that this is
> > a ***floating point*** standard, and I don't think that we particularly
> > care for decimal floating point.
>
> To be honest, I have little if any practical experience with numeric
> representation issues, but it seems sensible to me to implement a
> pre-existing, and presumably well thought out, standard, rather than
> inventing something ad-hoc.
>
> Of course, if the need is for something other than what ANSI 274
> standardises, which seems to be what you are implying, then fair
> enough. But what do you require? Infinite-precision decimal
> arithmetic? If so, can you explain how you'd handle something like
> 1/3?

As I said in the response to Alex, division is the one place where
fixed decimal gets into trouble. To repeat what I said to him,
I'd eliminate the division operators completely, and replace them
with a div(dividend, divisor, [resultplaces], [roundingpolicy])
operator.

The division operators make a hidden assumption that they
know what you want. That's ok for floating point, and it's
inherent for rationals, but it doesn't really work for integers
or fixed decimal.

In the spirit of explicit is better than implicit, I'd rather have
the control inherent in a div() operator.

>
> (I'm not being deliberately awkward here - my impression is that
> representation issues are *hard*, and there are a lot of traps you can
> fall into by oversimplifying. That's why I prefer the idea of a
> pre-existing standard: it implies that *someone* has thought about the
> hard stuff).
>
> > Floating point presumes limited precision. In other words, if the actual
> > number (exclusive of the location of the decimal point) gets too large,
> > the least significant part is ... thrown away. I don't want that.
> >
> > Since we've got infinite precision integer arithmetic, going to
> > limited precision decimal arithmetic is, IMNSHO, a step backwards.
>
> Even infinite precision integers throw away information, in some
> sense. Witness:
>
> >>> 1L//3L
> 0L

Same comment. Integer division, as it's currently implemented, is
simply wrong. However, we had that discussion and decided to
take one of the several different approaches, flying in the face
of the evidence that any choice was not going to be useable in
some context.

When you try to simplify an inherently complex situation by
putting a pretty face on it, all you do is confuse the issue more.

>
> >> When passing floating point to the constructor, what should happen?
> >>
> >> j. ``Decimal(1.1) == Decimal('1.1')``
> >> k. ``Decimal(1.1) ==
> >> Decimal('110000000000000008881784197001252...e-51')``
> >
> > Clearly, j is the correct answer. It's not all that hard to do, either.
>
> No way. Consider:
>
> >>> 1.1
> 1.1000000000000001
> >>> 1.1==1.1000000000000001
> True
>
> So what should Decimal(1.1000000000000001) evaluate to? It can't be
> Decimal('1.1'), as that contradicts your statement that j "clearly"
> applies. But it *also* can't be Decimal('1.1000000000000001'), as then
> we have the *same number* converting to two *different* Decimal
> values.
>
> As I say, it's hard.

Not that hard. It's not at all difficult to find where the actual number
ends and where the fuzz begins. You can do it visually, and the
algorithms to do it are quite well known. That's how printing libraries
handle the issue, after all.

You can also special case that with some lightweight compiler
magic. All that really has to happen is that the lexer has to pass
the 1.1 to the compiler without converting it to a float first,
then the parser can apply a special rule when it sees that token
in the context of decimal(1.1).


> I'd probably support Decimal(float) giving an exception, on the basis
> that if you're doing this, you probably don't know what you're getting
> into :-) Having a special method, say Decimal.round_float(f, digits),
> is probably OK, though...

I think someone earlier suggested (in the context of the Money type)
having the number of digits be an optional arguement to the constructor.
That is: decimal(1.1, 1) showing one place after the decimal point.

However, I prefer having the compiler take care of it.

John Roth

Irmen de Jong

unread,
Nov 1, 2003, 12:06:03 PM11/1/03
to
John Roth wrote:

> In the spirit of explicit is better than implicit, I'd rather have
> the control inherent in a div() operator.

+1 (without having read all of the thread, but John's
statement sounds very reasonable to me)

> I think someone earlier suggested (in the context of the Money type)
> having the number of digits be an optional arguement to the constructor.
> That is: decimal(1.1, 1) showing one place after the decimal point.
>
> However, I prefer having the compiler take care of it.

I think I don't. Consider:

d=decimal(1.1)

versus:

f = 1.1
d = decimal(f)

this would yield different results then. And I think that's confusing.

Although decimal(1.1,1) -with the extra argument "1 decimal place"-
isn't really pretty either, IMHO: you have to actually count the
number of decimal digits yourself!

--Irmen de Jong

Alex Martelli

unread,
Nov 1, 2003, 12:24:22 PM11/1/03
to
John Roth wrote:
...

> Not that hard. It's not at all difficult to find where the actual number
> ends and where the fuzz begins. You can do it visually, and the
> algorithms to do it are quite well known. That's how printing libraries
> handle the issue, after all.

There are several heuristics, but "printing libraries" (???) have nothing
to do with the issue. I have adopted one such heuristic (Stern-Brocot)
in gmpy at the request of P. Peterson, and there are other popular ones
such as Farey Fractions. But none is at all perfect.

> You can also special case that with some lightweight compiler
> magic. All that really has to happen is that the lexer has to pass
> the 1.1 to the compiler without converting it to a float first,
> then the parser can apply a special rule when it sees that token
> in the context of decimal(1.1).

So you are proposing that, in this *ONE PLACE* out of ALL the huge
variety in the Python language,

x = 1.1
...
y = decimal(x)

give a DIFFERENT result than

y = decimal(1.1)

??? This is just _not in the cards_. Python's regularity, uniformity,
and simplicity, are FAR more important than any one "special case" can
possibly be. Or, as the Zen of Python puts it, "special cases aren't
special enough to break the rules".


> I think someone earlier suggested (in the context of the Money type)
> having the number of digits be an optional arguement to the constructor.
> That is: decimal(1.1, 1) showing one place after the decimal point.

Constructing with some specified precision (not "SHOWING" but actually
constructing) would be fine. "Places after the decimal point" may or
may not be the ideal way to specify precision, that's a different issue
(if there are any applicable standards, I'd go for those, rather than
make any arbitrary decision in the matter). But letting the precision
default if left unspecified -- and thus letting construction from floats
just happen -- is a far different decision. Naive users will always
_believe_ that they're getting "good" precision, if they think at all
about the matter (which unfortunately they may not), unless they _are_
forced to think about the subject by needing to specify precision very
explicitly. Thus, I think "construction from float with some default
precision" runs a substantial risk of tricking naive users.


Alex

John Roth

unread,
Nov 1, 2003, 12:32:57 PM11/1/03
to

"Alex Martelli" <al...@aleax.it> wrote in message
news:a%Rob.394217$R32.13...@news2.tin.it...
> John Roth wrote:
> ...

[snip]
I decided to snip the prior piece rather than argue about your
misconception of what I intended. This would have been obvious
if you had left the context of my comment in, rather than starting
it out with my response to something invisible to the reader.

> > I think someone earlier suggested (in the context of the Money type)
> > having the number of digits be an optional arguement to the constructor.
> > That is: decimal(1.1, 1) showing one place after the decimal point.
>
> Constructing with some specified precision (not "SHOWING" but actually
> constructing) would be fine. "Places after the decimal point" may or
> may not be the ideal way to specify precision, that's a different issue
> (if there are any applicable standards, I'd go for those, rather than
> make any arbitrary decision in the matter). But letting the precision
> default if left unspecified -- and thus letting construction from floats
> just happen -- is a far different decision. Naive users will always
> _believe_ that they're getting "good" precision, if they think at all
> about the matter (which unfortunately they may not), unless they _are_
> forced to think about the subject by needing to specify precision very
> explicitly. Thus, I think "construction from float with some default
> precision" runs a substantial risk of tricking naive users.

I agree with that. I'd just as soon require that the precision
be specified if the input is a float.

As far as using number of places after the decimal point, rather
than some other unit, I will admit that I can't think of another unit.

John Roth
>
>
> Alex
>


John Roth

unread,
Nov 1, 2003, 12:33:51 PM11/1/03
to

"Irmen de Jong" <irmen@-NOSPAM-REMOVETHIS-xs4all.nl> wrote in message
news:3fa3e7fb$0$58697$e4fe...@news.xs4all.nl...

See Alex's comment, and my response. I agree it isn't pretty,
but the alternatives seem to be worse.

John Roth
>
> --Irmen de Jong
>


Alex Martelli

unread,
Nov 1, 2003, 1:24:44 PM11/1/03
to
John Roth wrote:
...

> Alex, where did I suggest that I wanted a rational data type? Please
> tell me one place in my response where I said that. Please?

You fought against limited precision, and said NOTHING against
the requirement that the new type support division (point 12 in
the specs). This implies the implementation must use rationals
(by any other name). Of course, if you now change your responses
(and in particular change the lack of objection to / into a
request that it be abrogated, as you do below) then (at least
some of) the objections to your proposals change (the one against
the crazy idea of having number+string implicitly convert the
string "just as if" it had been explicitly converted stands, of
course -- "if you want Perl, you know where to find it").


> For the record. I'm not suggesting a rational data type. I'm quite
> well aware of the arguements pro and con, and Guido's position.
> Please read what I said without your preconceptions.

I have no preconceptions about what your opinions of various
number kinds may be: I simply read your post, and if in that
post you say *NOT A WORD* against division, how can you expect
any reader to divine that you want to abolish it?! Cheez --
what about learning to write, rather than chiding others for
not reading your mind but rather just reading what you write
and what you _don't_ write?!


> The only place where you can get into trouble is with division
> and equivalent operations. That's the one place where you actually

If one accepts that an arbitrary float is somehow (perhaps a bit
arbitrarily) coerced into a decimal-fraction before operating
(e.g. by multiplication) -- or forbids such mixed-type operations,
against your expressed wishes -- yes.

The resulting decimal type, however, may not be highly usable
for some kinds of monetary computations. It's an unfortunate
but undisputable fact of life that laws and regulations exist
that specify some monetary computations in detailed ways that
differ from each other. E.g., in the EU all computations must
be carried out to the hundredth of an Euro, no less and *no
more*, with rounding as specified in the regulations (I quoted
them extensively in the preceding python-dev discussion which
you may find in the archives) -- the laws indicate that this
may give "off by one cent" results compared with exact arithmetic,
with examples, and mandate that this one-cent difference be
accepted as it derives from application of their exact rules
(they even specifically note that reconciling accounts will be
made too hard unless computer programs and similar equipment
all follow these exact rumes). Other jurisdictions have
different rules and regulations -- e.g., not sure if there still
are such markets, but once US financial markets mandated the
accounting to be in 1/16th of a dollar (which is _not_ the same
thing as arithmetic in hundredths of a cent -- most values you
might obtain with the latter are not valid in the former).

Say that a charge that is specified by law as 2.6473% is to be
computed on each unit of a stock currently worth 2.33 currency
units. The resulting "exact" amount of 0.06168209 must be
treated as "exactly" 6 Eurocents in the EU; this will cause an
off-by-one (and thus by a lot depending how many units of stock
you have), but if this is the computation the law specifies,
this is the one you have to perform. If laws and regulations
mandated accounting in 16th of a currency unit, you'd have to
consider that amount as 1/16th, i.e. 0.625 -- "erring" the other
way. In neither case would it be acceptable to carry around the
"exact" amount and then say, for example, to a customer who owns
1000 units of the stock, that he must pay a charge of 6.168209
dollars or euros (not even if you rounded it to 6.17 _at this
stage_) -- in one case you would overcharging by 16.8209 (or 17
cents), in the other, undercharging by 8 cents wrt the 6.25 the
tax authorities will later want.

I realize worrying about these 8 or 17 cents _seems_ silly, but,
it IS what accountants *DO* -- and if one writes accounting
software one should really play by their rules (or be Microsoft,
who feels free to have "rules" in VB and [different ones, I'm
told] Excel arithmetic which drive accountants batty:-).

Note that the operation that gives trouble here is not a
division -- it's a multiplication. The problem is with carrying
around "unbounded precision" _when laws and regulations
tell you otherwise_. Forcing the user to remember to round
again (by calling the appropriate rounding function) after every
multiplication is dangerous, because subtly wrong but plausible
results will come if the user forgets one of those calls. The
sensible approach would seem to be to imbue every Decimal instance
with the rounding rules that instance is supposed to follow (in
most programs the rules will be the same for every instance, but
_allowing_, albeit with some trouble if need be, particularly
complicated programs to carry on computations with several
different sets of rules, would be good -- as long as no accidental
and incorrect "mix" of them can happen by mistake).


There may be use cases where "unbounded precision" is the rule
that an application needs and yet ease of division is not needed.

I'm not sure there are any easy examples (e.g., in a purely
indicative "what if" scenario it might be cool to keep unbounded
precision if performance allowed -- although in that case I
suspect "error-bounds propagation" might be even more useful --
but for such [not-necessarily-quick-but]-dirty computations, the
inability to just divide with / would be a bit cumbersome) but
surely there may be some (it's up to advocates of your variant
of "unbounded precision" to come up with real use cases for them,
of course). But the bread-and-butter use cases for computations
with money don't require unbounded precision and in fact may be
worse off with it when it conflicts with laws and regulations --
specified precision rules per Decimal instance looks therefore like
a vastly preferable solution, and the special case of unbounded
precision may be handled either by allowing "unbounded precision"
as one of the special precision/rounding rules sets, or making a
specialcase 'unboundeddecimal' type, perhaps a subclass of the
ordinary bounded-precision decimal type, if implementation turns
out to be simpler that way.

> need to specify the result number of decimal places and the rounding
> policy. Every other operation has a well defined result that won't
> ever lead to repeating decimal representations, etc.

No, but repeating decimals are not the only problem; and the rules
of unbounded precision are not the only ones in town, so the "well
defined result" they produce need not be the same as the "well
defined result" produced by other rules which laws or regulations
(more often than not based on specified finite precision) mandate.


> My basic suggestion for that is to replace the division operators
> with a div() function that lets you specify the number of places
> and the rounding policy.

I have no issue with that, but I definitely think that, to actually
be USEFUL in practice, Decimal instances should be able to carry
around their own precision and rounding-rules. Then you can use
explicit div and mul (functions or methods) when you want to
explicitly specify something different -- probably add and sub too,
when you want to produce a Decimal that may have different rules
as a result, or explicitly "mix" (operate betweem) instances that
might have different and possibly conflicting rules. But you can
also use the ordinary operators in ordinary circumstances when you
are operating between instances that have the same rules. In
this case, I think that add(a,b) , mul(a,b) , etc, without specific
parameters for precision, rounding, nor other rules, might be
totally equivalent to a+b , a*b , etc. It costs nothing and it
might endear us a little bit to the "migrating from Cobol" crowd
(OK, not as much as "add a to b" would, but we can't have that:-).


Alex

John Roth

unread,
Nov 1, 2003, 2:10:15 PM11/1/03
to

"Alex Martelli" <al...@aleax.it> wrote in message
news:MTSob.78975$e5.29...@news1.tin.it...

Alex, I think we've lost context, so I'm going to state,
up front, the context I see for the discussion. More
detail is at the back of this post.

I'm quite happy with the notion of all the messy accounting
and regulatory details being handled by a money type that
is designed to keep the accountants and regulators happy,
at the expense of programming simplicity. I spent quite a
few years doing that type of programming; I think I know
a bit about it.

Given that, I don't see any real advantage in having a separate
decimal type that duplicates the functionality needed for
money. The decimal type should be directed more toward the
intuitive, ease of use angle that Python is famous for.

I also don't see a case for a floating decimal type. If you
have the money type, then there isn't a whole lot that
you can do with floating decimal that you can't do with
regualar binary floats.

I can see some justification for a simple, straightforward,
fixed decimal type that makes reasonable assumptions in
corner cases, while still allowing the programmer a good
deal of control if she wants to exercise it.

That's the context of my remarks.


> John Roth wrote:

> ...
> > Alex, where did I suggest that I wanted a rational data type? Please
> > tell me one place in my response where I said that. Please?
>
> You fought against limited precision, and said NOTHING against
> the requirement that the new type support division (point 12 in
> the specs). This implies the implementation must use rationals
> (by any other name).

The alternative explanation is that I simply hadn't thought that
part of the issue through when I made the response. It's a
much simpler explanation, isn't it?

> Of course, if you now change your responses
> (and in particular change the lack of objection to / into a
> request that it be abrogated, as you do below) then (at least
> some of) the objections to your proposals change (the one against
> the crazy idea of having number+string implicitly convert the
> string "just as if" it had been explicitly converted stands, of
> course -- "if you want Perl, you know where to find it").

That's discussable, of course.

>
> > The only place where you can get into trouble is with division
> > and equivalent operations. That's the one place where you actually
>
> If one accepts that an arbitrary float is somehow (perhaps a bit
> arbitrarily) coerced into a decimal-fraction before operating
> (e.g. by multiplication) -- or forbids such mixed-type operations,
> against your expressed wishes -- yes.

If we're going to have to specify additional information
when we explicitly construct a decimal from a float, as
one variety of proposal suggests, then I see no difficulty
with prohibiting implicit conversions. In fact, I seem to
remember a note that implicit type coercion may vanish
sometime in the misty future (3.0 time frame.)

> The resulting decimal type, however, may not be highly usable
> for some kinds of monetary computations.

I think that was the justification for a separate money
data type.

> It's an unfortunate
> but undisputable fact of life that laws and regulations exist
> that specify some monetary computations in detailed ways that
> differ from each other.

Understood, in detail.

[...]

> Note that the operation that gives trouble here is not a
> division -- it's a multiplication. The problem is with carrying
> around "unbounded precision" _when laws and regulations
> tell you otherwise_. Forcing the user to remember to round
> again (by calling the appropriate rounding function) after every
> multiplication is dangerous, because subtly wrong but plausible
> results will come if the user forgets one of those calls. The
> sensible approach would seem to be to imbue every Decimal instance
> with the rounding rules that instance is supposed to follow (in
> most programs the rules will be the same for every instance, but
> _allowing_, albeit with some trouble if need be, particularly
> complicated programs to carry on computations with several
> different sets of rules, would be good -- as long as no accidental
> and incorrect "mix" of them can happen by mistake).

That is, in fact, the way money needs to be handled. However,
I was under the impression that the separate money type was
still in play, for the reasons stated in the pre-pep.

[...]

> I have no issue with that, but I definitely think that, to actually
> be USEFUL in practice, Decimal instances should be able to carry
> around their own precision and rounding-rules. Then you can use
> explicit div and mul (functions or methods) when you want to
> explicitly specify something different -- probably add and sub too,
> when you want to produce a Decimal that may have different rules
> as a result, or explicitly "mix" (operate betweem) instances that
> might have different and possibly conflicting rules. But you can
> also use the ordinary operators in ordinary circumstances when you
> are operating between instances that have the same rules. In
> this case, I think that add(a,b) , mul(a,b) , etc, without specific
> parameters for precision, rounding, nor other rules, might be
> totally equivalent to a+b , a*b , etc. It costs nothing and it
> might endear us a little bit to the "migrating from Cobol" crowd
> (OK, not as much as "add a to b" would, but we can't have that:-).

The base problem with this is that COBOL doesn't do it that
way, and COBOL was deliberately designed to do things the
way the accounting profession wanted, or at least make it
possible to do them without the language getting in your way.

Part of the reason why COBOL has the separate operators
is that the *destination* of the operation specifies how the
result is computed. You can't do that with intermediate
results if you use expression notation.

The only way you can do anything similar in a language like
Python is to avoid the operators and use functions or methods
that allow you to explicitly specify the exact form of the result,
together with the rounding rules, if any, used to get there.

Another thing that hasn't been brought up, though: COBOL
also allows you to specify a maximum for a value: you can't
exceed it without causing an overflow exception (which can
be caught with an ON OVERFLOW clause, of course.)

John Roth


>
>
> Alex
>


Alex Martelli

unread,
Nov 1, 2003, 2:19:39 PM11/1/03
to
John Roth wrote:
...

> I decided to snip the prior piece rather than argue about your
> misconception of what I intended. This would have been obvious
> if you had left the context of my comment in, rather than starting
> it out with my response to something invisible to the reader.

I prefer to trim posts from such generally irrelevant history,
but if you think "This would have been obvious" here is ALL you
had to say in your first post in response to the point, which
you quoted in full, about what operators should apply to decimals:

"""
> 12. To support the basic aritmetic (``+, -, *, /, //, **, %, divmod``) and
> comparison (``==, !=, <, >, <=, >=, cmp``) operators in the following
> cases:
>
> - Decimal op Decimal
> - Decimal op otherType
> - otherType op Decimal
> - Decimal op= Decimal
> - Decimal op= otherType
>
> Check `Items In Discussion`_ to see what types could OtherType be, and
> what happens in each case.

> 13. To support unary operators (``-, +, abs``).

OK.
"""

Now try to argue _with a straight face_ that, quoting this part entirely, it
"would have been obvious" that you wanted to abrogate the applicability of
normal division operators to decimals, and therefore did not need as your
cherished "unbounded precision decimal" a full rational number in some
form. Pah.

Assuming that's what you intended in that post, I think you made a huge
mistake in NOT saying so, rather just placing a meek "OK" there, and are
now trying to imply that instead of your huge mistake there were some
"misconception" (or as you said earlier, even LESS defensibly!,
"preconceptions" [!!!]) on MY part. In my view of the world, it's all
right to make a mistake (we're humans), but it's NOT ok to try to attack
others rather than admitting and apologizing for one's mistake.


Alex

Ron Adam

unread,
Nov 1, 2003, 2:32:39 PM11/1/03
to
On Fri, 31 Oct 2003 15:36:38 -0300, "Batista, Facundo"
<FBat...@uniFON.com.ar> wrote:

[clip]

>
>The Decimal data type should support the Python standard functions and
>operations and must comply the decimal arithmetic ANSI standard X3.274-1996.

Is this the correct ANSI number? I ended up at the following
websight.

http://www.rexxla.org/Standards/standards.html


_Ronald R. Adam


John Roth

unread,
Nov 1, 2003, 3:51:18 PM11/1/03
to

"Alex Martelli" <al...@aleax.it> wrote in message
news:fHTob.394884$R32.13...@news2.tin.it...

Alex, this whole pissing match started because *YOU* assumed,
without any justification that I could see, that I was suggesting that
we use rational numbers.

You devoted several paragraphs in what I took to be a highly
patronizing explanation of things I know very well, and in any
case had no intention of getting into in the context of this pre-pep.

I have attempted to set this straight in the next post on the other
chain of this thread. You're the one that made the blunder. You're
the one that owes me the appology.

John Roth
>
>
> Alex
>


Alex Martelli

unread,
Nov 1, 2003, 5:10:17 PM11/1/03
to
John Roth wrote:

> "Alex Martelli" <al...@aleax.it> wrote in message
> news:MTSob.78975$e5.29...@news1.tin.it...
>
> Alex, I think we've lost context, so I'm going to state,
> up front, the context I see for the discussion. More
> detail is at the back of this post.
>
> I'm quite happy with the notion of all the messy accounting
> and regulatory details being handled by a money type that
> is designed to keep the accountants and regulators happy,
> at the expense of programming simplicity. I spent quite a
> few years doing that type of programming; I think I know
> a bit about it.

Most of the "expense of programming simplicity" can be hidden from
application programs and placed in a suitable "decimal arithmetic"
type. As per http://www2.hursley.ibm.com/decimal/ , "a single data
type can be used for integer, fixed-point, and floating-point decimal
arithmetic" -- and for money arithmetic which doesn't drive the
application programmer crazy, too, as long as:

1. the existing standards are respected -- this means, again as
per the quoted site, IEEE 754R + IEEE 854 + ANSI X3.274 + ECMA 334

2. specifying the rounding mandated by typical existing laws and
regulations (e.g., the EU ones) is made reasonably easy for
those typical applications that must apply such typical rules
throughout

3. there are ways to specify other arbitrary sets of rules, and
handle difficult cases such as more than one such set in use
within a single program (but it's clear that a general approach
to this point [3] may not achieve ease and simplicity)

4. syntax for cases sub [2] is adequately easy and Pythonic

In other words, _no way_ a Pythonista will want to code some vaguely
pythonic equivalent of (http://www2.hursley.ibm.com/decimal/dnusers.html):

18. decNumberDivide(&rate, &rate, &hundred, &set); // rate=rate/100
19. decNumberAdd(&rate, &rate, &one, &set); // rate=rate+1
20. decNumberPower(&rate, &rate, &years, &set); // rate=rate**years
21. decNumberMultiply(&total, &rate, &start, &set); // total=rate*start

rather than, e.g.:

total = start * (1 + rate/100)**years

or something like this. As long as 'start' and 'rate' are suitable
instances of Decimal, carrying appropriate precision and rules (the
"decContext set" that here is being passed boilerplately at each
painstaking step), there is nothing ambiguous nor dangerous here.


> Given that, I don't see any real advantage in having a separate
> decimal type that duplicates the functionality needed for
> money. The decimal type should be directed more toward the
> intuitive, ease of use angle that Python is famous for.

The Decimal type discussed in this PEP is the arithmetic fundament
for Money. Facundo started out with the idea of a PEP for a type
named Money that mixed arithmetic, parsing and formatting concerns.

He received ample and detailed feedback, on the basis of which he
(quite reasonably, IMHO) concluded that a Decimal type, based on
existing nondist/sandbox implementations that realized Cowlishaw's
ideas and work (as per the first URL above) would be the right
fundament for a Money type (which could either subclass or use it
and add parsing and formatting niceties as needed).

So, this Decimal type isn't "duplicating" anything: it's intended
to _supply_ the arithmetic features needed (inter alia) by money
computations.

> I also don't see a case for a floating decimal type. If you
> have the money type, then there isn't a whole lot that
> you can do with floating decimal that you can't do with
> regualar binary floats.

We won't "have a money type" unless its arithmetic can be
handled by a suitable Decimal class (or intermixed with parsing
and formatting in an overcomplicated class, but I would consider
that an inferior choice).

What you can do with Decimal (fixed or floating point) is
basically to respect the "principle of least surprise" for
the innumerable newbies who are overwhelmed by the concept
that, e.g., "1.1" is displayed as 1.100000000000000001 with
full precision. Such newbies do not necessarily expect that
(e.g.) (1/3)*3 == 1 -- basically because they have used
calculators, which are invariably based on fixed or floating
point decimal arithmetic with bounded precision. They *DO*
expect to be able to write "the four operations" simply.

ABC's idea was to meet these newbies' concerns by using
rationals. Rexx, which has always used decimal arithmetic
instead, has a better track record in this respect.

There may be a niche for unbounded decimal precision, either
as a special case of Decimal, a subtype thereof, or a completely
independent numeric type. But if that type requires giving up
on the handy use of / and % -- I predict newbies will shun it,
and they will have a point (so we should not _foist_ it on them
in preference to bounded-precision Decimals that _DO_ let them
do divisions with normal rules).

One further detail you should note is that Python (out of the
box, i.e. without such extensions as gmpy) doesn't let you
have binary floating point numbers *with whatever precision
you specify*: you're limited to what your hardware supplies.
If you need, e.g., 1024 measly bits of precision -- TOUGH.
Decimal, be it used as a fixed or floating point number, should
suffer from no such limitation: whatever bounded precision you
may specify on number creation (your memory permitting) should
work just as well. Just that fact will allow a range of tasks
which are hard to handle with Python's floats -- not because of
binary vs decimal issues, but because Python's floats are just
too limited for some tasks (gmpy.mpf instances would be fine
even though they're binary, Decimal instances would be fine
even though they're decimal).


> I can see some justification for a simple, straightforward,
> fixed decimal type that makes reasonable assumptions in
> corner cases, while still allowing the programmer a good
> deal of control if she wants to exercise it.

I do not think Decimal is limited to either fixed or floating
point. The Hursley site is quite specific in claiming that
both can be supported in a single underlying type. Unbounded
precision is a different issue.


>> > Alex, where did I suggest that I wanted a rational data type? Please
>> > tell me one place in my response where I said that. Please?
>>
>> You fought against limited precision, and said NOTHING against
>> the requirement that the new type support division (point 12 in
>> the specs). This implies the implementation must use rationals
>> (by any other name).
>
> The alternative explanation is that I simply hadn't thought that
> part of the issue through when I made the response. It's a
> much simpler explanation, isn't it?

If you advocate a right triangle whose cathets (two shortest sides)
are of length 3 and 4, you cannot then feel outraged if others claim
you advocated a triangle with a hypotenuse of length 5. The obvious
and inevitable mathematical consequences of what you DO advocate are
fully part of your advocacy -- and if you haven't thought such
elementary aspects through, then when they're rubbed in your face
you could admit your mistake, and change your position, rather than
try attacking those who may have thought thinks through a little more
thoroughly (or may be simply so familiar with the issues that the
consequence Z of certain premises X and Y are utterly obvious to them).


>> the crazy idea of having number+string implicitly convert the
>> string "just as if" it had been explicitly converted stands, of
>> course -- "if you want Perl, you know where to find it").
>
> That's discussable, of course.

Sure, everything is. x+"23" may raise an exception when x is
a number of type int, long, float, OR complex, and STILL when x
is a number of type decimal entirely different and ad hoc rules
COULD apply, just in order to astonish everybody I assume.

Are you actually planning to DEFEND this ridiculous contention,
or just claiming it's "discussable" in some abstract philosophical
way? Just checking...:-).


>> > The only place where you can get into trouble is with division
>> > and equivalent operations. That's the one place where you actually
>>
>> If one accepts that an arbitrary float is somehow (perhaps a bit
>> arbitrarily) coerced into a decimal-fraction before operating
>> (e.g. by multiplication) -- or forbids such mixed-type operations,
>> against your expressed wishes -- yes.
>
> If we're going to have to specify additional information
> when we explicitly construct a decimal from a float, as
> one variety of proposal suggests, then I see no difficulty
> with prohibiting implicit conversions. In fact, I seem to
> remember a note that implicit type coercion may vanish
> sometime in the misty future (3.0 time frame.)

Yes, coercion is going -- that basically means that e.g. an
__add__(self, other) (etc) method should deal with all types
of 'other' that the type of 'self' is prepared to deal with,
rather than factoring out all of the type-conversion issues into
__coerce__. Basically an acknowledgment that conversions may
too often need to be different for different operations.


>> The resulting decimal type, however, may not be highly usable
>> for some kinds of monetary computations.
>
> I think that was the justification for a separate money
> data type.

Money often needs to get into arithmetic computation with
"pure numbers" -- numbers that do not directly measure an
amount of money. For example, in compound interest
computations, the monetary amounts basically come in at
the very end, in multiplications by pure numbers which are
previously computed without reference to the monetary unit.

So, I don't think the "money data type" can do without a
suitable. purely arithmetical data type that is basically
the Decimal being discussed here (with or without possible
extension to "unbounded precision" -- but with the need
for e.g. raising-to-power, yet another of those "division-
equivalent" [or worse] operations, I have my doubts there).

Therefore, the idea that Money uses (perhaps by subclassing)
Decimal, and further adds (e.g.) parsing and formatting
aspects (not needed for the pure numbers that so often have
arithmetic together with Money, while the aritmetic aspects
ARE needed throughout), seems sound to me.

> I was under the impression that the separate money type was
> still in play, for the reasons stated in the pre-pep.

Sorry, I don't see any mentions of money in the prePEP as
Facundo posted it here on Friday -- perhaps you can quote
the relevant parts of that post?


> The base problem with this is that COBOL doesn't do it that
> way, and COBOL was deliberately designed to do things the
> way the accounting profession wanted, or at least make it
> possible to do them without the language getting in your way.
>
> Part of the reason why COBOL has the separate operators
> is that the *destination* of the operation specifies how the
> result is computed. You can't do that with intermediate
> results if you use expression notation.
>
> The only way you can do anything similar in a language like
> Python is to avoid the operators and use functions or methods
> that allow you to explicitly specify the exact form of the result,
> together with the rounding rules, if any, used to get there.

Yes, _when_ you need that -- which fortunately is not all that
common. Basically, you can control something based on the
type of a destination only via augumented assignment -- say +=
as the equivalent to "add a to b" -- rather than via the equivalent
of "add a to b giving c", and the control (coming "from the
destination") doesn't extend to intermediate results.

Also, making mutable numbers (so that += allows some control,
or so that you can have a .set method to control e.g. overflow
wrt a max on assignment) is not very Pythonic.

Most of the time, though, the rules can just as well be
embodied in the operands as in the result -- and don't change
operation by operation.


> Another thing that hasn't been brought up, though: COBOL
> also allows you to specify a maximum for a value: you can't
> exceed it without causing an overflow exception (which can
> be caught with an ON OVERFLOW clause, of course.)

Given the difficulties with mutable numbers, maybe the best
way to do that in Python is with a 'raiseifabove' function.
Or maybe a "settable-ONCE" number with a flag that records
whether it's already been initialized or not is acceptable,
though instinctively it feels a bit clunky to me.


Alex

Alex Martelli

unread,
Nov 1, 2003, 5:34:28 PM11/1/03
to
John Roth wrote:
...

> Alex, this whole pissing match started because *YOU* assumed,
> without any justification that I could see, that I was suggesting that
> we use rational numbers.

-- you wanted (and want) unlimited precision
-- all you had to say about division was "OK"

it follows that rational numbers are _inevitably_ necessary.

If you can't see that 2 plus 2 is 4, that's YOUR fault, not mine.

Similarly, if you can't see "any justification" for somebody "assuming"
(HA) that when you say you want 2 plus 2, you've essentially *SAID* you
want 4, you need a course in logic and commonsense.

> I have attempted to set this straight in the next post on the other
> chain of this thread. You're the one that made the blunder. You're
> the one that owes me the appology.

I entirely disagree with your assertion. YOU "made the blunder", as you so
charmingly put it, by just OK'ing something which could NOT possibly be OK
within the way you conceived your proposal -- you accepted division AND
still argued for unlimited precision, THEREFORE what you were advocating
HAD, inevitably, to include rationals (by whatever name or possibly
roundabout implementation, who cares).

You didn't think things through, is how you put it in another post.
Very well, but then that is entirely *YOUR* fault, NOT mine. If you
ask for 2+2 because you haven't bothered to think things through, it's
NOT "a blunder" of any kind on my part to claim that you've asked for 4:
it's a blunder on YOUR part not to have considered the obvious, and
it is gross rudeness to accuse others of "blunders" and demand apologies
because of YOUR failings and mistakes.

But then, I somehow find this goes very fittingly with your advocacy
that x+"23" should NOT rase an exception when x is a decimal, even though
it DOES raise an exception when x is an int, a long, a float, a complex
(current status on the matter, you claim it's "discussable", I think,
rather than admitting it's OBVIOUSLY absurd).

Such idiotic notions, and behavior as incredibly rude as you are
displaying, may often go hand in hand, I've noticed.


Alex

John Roth

unread,
Nov 1, 2003, 5:34:56 PM11/1/03
to

"Alex Martelli" <al...@aleax.it> wrote in message
news:dbWob.79646$e5.29...@news1.tin.it...

> John Roth wrote:
>
> > "Alex Martelli" <al...@aleax.it> wrote in message
> > news:MTSob.78975$e5.29...@news1.tin.it...
> >
> > Alex, I think we've lost context, so I'm going to state,
> > up front, the context I see for the discussion. More
> > detail is at the back of this post.

[snip]

I'm going to quit this discussion, since you seem to have
some kind of snit to pick, and are hardly being rational.

One of the major things you do when you're having a
civil discussion is, when you don't understand something,
assume that the other person meant something reasonable
by it. You don't seem to be doing that. One example
(out of several that I'm not going to bother with) follows.


>
> > I was under the impression that the separate money type was
> > still in play, for the reasons stated in the pre-pep.
>
> Sorry, I don't see any mentions of money in the prePEP as
> Facundo posted it here on Friday -- perhaps you can quote
> the relevant parts of that post?

You don't see it in ***that*** pre-pep because it, quite
clearly, isn't in there. There also isn't anything in there that
says the Friday pre-pep replaces the previous one about
a money type either.

A reasonable, not to say charitable, interpretation of what
I said would have been to understand that I was refering
to the previous pre-pep, about the money type.

Bye.
John Roth


Mel Wilson

unread,
Nov 1, 2003, 12:23:41 PM11/1/03
to
In article <vq7ogq9...@news.supernews.com>,

"John Roth" <newsg...@jhrothjr.com> wrote:
>As I said in the response to Alex, division is the one place where
>fixed decimal gets into trouble. To repeat what I said to him,
>I'd eliminate the division operators completely, and replace them
>with a div(dividend, divisor, [resultplaces], [roundingpolicy])
>operator.

I don't really like this, because it makes Decimals look
unlike numbers.

But if I had to live with it, I'd probably wind up coding
things like

factor1 = Decimal ('0d.333') # 1/3 to 3 places
sales_tax_rate = Decimal (7, -2) # .07

(where '0d.333' is something I just cooked up as a
compromise between avoiding Decimal+string arithmetic and
accepting the fact that a series of digits is the best way
to express a decimal number.)

Then the standard multiply would do what I wanted, and I
would have said, in building the factors, exactly what I
thought I was doing.

I admit that the implementation I'm playing with doesn't
implement __div__ or __truediv__ yet .. because I can't
figure out the right way.

Regards. Mel.

Bengt Richter

unread,
Nov 1, 2003, 8:41:35 PM11/1/03
to
On Fri, 31 Oct 2003 15:36:38 -0300, "Batista, Facundo" <FBat...@uniFON.com.ar> wrote:

>Here I send it.
>
>Suggestions and all kinds of recomendations are more than welcomed.
>
>If it all goes ok, it'll be a PEP when I finish writing/modifying the code.
>
>Thank you.
>
>. Facundo
>

(First, thanks for the plain-text post ;-)


>
>------------------------------------------------------------------------
>
>PEP: XXXX
>Title: Decimal data type
>Version: $Revision: 0.1 $
>Last-Modified: $Date: 2003/10/31 15:25:00 $
>Author: Facundo Batista <fbat...@unifon.com.ar>
>Status: Draft
>Type: Standards Track
>Content-Type: text/x-rst
>Created: 17-Oct-2003
>Python-Version: 2.3.3
>
>
>Abstract
>========
>
>The idea is to have a Decimal data type, for every use where decimals are
>needed but floating point is too inexact.
>
>The Decimal data type should support the Python standard functions and
>operations and must comply the decimal arithmetic ANSI standard X3.274-1996.

Isn't that a REXX standard?

(<rant_alert>
BTW, I really dislike pay-to-view standards! IMO any reader taking the time
to read and understand a national standard is contributing more than a reasonable cover price
to society, so why not subsidize ANSI with the very few seconds worth of the 24/7 military budget
(~15k$/sec continuous world rate, mostly US) that would equal their standards-sales income?
</rant_alert>)

>
>
>Rationale
>=========
>
>I must separate the requeriments in two sections. The first is to comply
>with the ANSI standard. All the needings for this are specified in the
>Mike Cowlishaw's work at http://www2.hursley.ibm.com/decimal/. Cowlishaw's
>also provided a **lot** of test cases. The second section of requeriments
>(standard Python functions support, usability, etc) are detailed in the
>`Requirements`_ section.
>
>Here I'll include all the decisions made and why, and all the subjects still
>being discussed. The requirements will be numbered, to simplify discussion
>on each point.
>
>This work is based on code and test functions written by Eric Price, Aahz
>and
>Tim Peters. Actually I'll work on the Decimal.py code in the sandbox (at
>python/nondist/sandbox/decimal in SourceForge). Some of the explanations of
>this PEP are taken from the Cowlishaw's work.
>
>
>Items In Discussion
>-------------------
>
>When in a case like ``Decimal op otherType`` (see point 12 in Requirements_
>for details), what should happen?
>
> if otherType is an int or long:
>
> a. an exception is raised

If conversion is not feasible within some constraints, and there is no conversion-failure
handler specified? IOW, I think maybe this Decimal type class should be designed to be
subclassable in convenient ways to handle corner cases, etc.

> b. otherType is converted to Decimal

IWT this would be the normal default, since int and long can be presumed to be exact.

> c. Decimal is converted to int or long (with ``int()`` or
>``long()``)
>
> if otherType is a float:
>
> d. an exception is raised

if no handler is specified and precision default is not specified in subclassing or
is riskily near available bits.



> e. otherType is converted to Decimal (rounding? see next item in
> discussion)

yes, but with mandatory existence of precision info specified in subclass

> f. Decimal is converted to float (with ``float()``)
>
> if otherType is a string:
>
> g. an exception is raised

if no handler


> h. otherType is converted to Decimal
> i. Decimal is converted to string (bizarre, huh?)
>
>
>When passing floating point to the constructor, what should happen?
>
> j. ``Decimal(1.1) == Decimal('1.1')``
> k. ``Decimal(1.1) ==
>Decimal('110000000000000008881784197001252...e-51')``

IMO the base class constructor should refuse, unless precision info is also passed,
but a subclass could have a default precision, and accept anything that can be rounded
by a specified rule with a specified margin of possible error to the exact internal
representation. IMO both j and k should raise an exception due to the Decimal(1.1), and
Decimal('110000000000000008881784197001252...e-51') should maybe generate a warning,
at least optionally. I.e., string literals do specify precision exactly, I presume?
I.e., '1.1' means something different from '1.1000', even though hopefully
Decimal('1.1')==Decimal('1.1000').


>
>
>Requirements
>============
>
>1. The syntax should be ``Decimal(value)``.

Decimal(value, precision_info) when the value cannot be presumed exact, IWT.


>
>2. The value could be of the type:
>
> - another Decimal

presume this is just a copy, unless precision info is provided?
> - int or long
assume no-fractional-bits precision?
> - float
IMO this case should always require precision info for the base class contructor
> - string
assume redundant zeroes on right of decimal point indicate exact precision as required,
even if there is an exponent? I.e., what does '1.000e3' mean? Same as '1000' or
'1000.000' ? What about '1.000e-2' ? Same as '0.010' or as '0.01' ?


>
>3. To exist a Context. The context represents the user-selectable
>parameters
> and rules which govern the results of arithmetic operations. In the
> context the user defines:
>
> - what will happen with the exceptional conditions.
> - what precision will be used
> - what rounding method will be used
>
>4. The Context must be omnipresent, meaning that changes to it affects all
> the current and future Decimal instances.

That sounds like a recalculate event on a spread sheet, and ISTM you could not
anticipate general requirements in the primitive type.

I'm not sure what you mean, but one interpretation of Context could be a separate class
that makes customizable number containers that enforce rules about their Decimal instance
contents. Thus you could write, e.g.,

class EuroBox(object):
import Decimal
# subclass Decimal for internal customized representation
class Euro(Decimal):
def __init__(self, default_precision=4): # or whatever
# ...
# ...
def __setattr__(self, name, value):
# ... convert new EuroBox item to Euro (a subclass of Decimal) instance

euro_box = EuroBox()
euro_box.my_money = '0.02' # EuroBox class creates Euro instance and binds to 'my_money' attr.
# ... etc

So here the Context is outside of Decimal per se, and doesn't imply any readjustment magic
inside of Decimal, just the ability to be subclassed and have hooks to make the magic easier
to implement.

The idea (OTTOMH ;-) is that EuroBox could possibly also enforce non-Decimal-relevant _legal_
rounding rules etc., and let you decorate the Euro sublass with localized or app-specific
__str__ and __repr__ etc. as well. But such stuff is outside the realm of Decimal per se.
I am just speculating on uses to trigger thinking on making _support_ of such stuff smooth,
definitiely _not_ to have it built in.

I haven't read all the background, so if I am envisioning something that clashes with plans,
feel free to ignore me ;-)

Maybe there needs to be an abstract base class that you _have_ to subclass and
specify all these things?

>10. Strings with floats in engineering notation will be supported.
>
>11. Calling repr() should do round trip, meaning that::
>
> m = Decimal(...)
> m == eval(repr(m))

Does that mean repr(m) will always look like "Decimal('<string literal>')" ?

>
>12. To support the basic aritmetic (``+, -, *, /, //, **, %, divmod``) and
> comparison (``==, !=, <, >, <=, >=, cmp``) operators in the following
> cases:
>
> - Decimal op Decimal
> - Decimal op otherType
> - otherType op Decimal
> - Decimal op= Decimal
> - Decimal op= otherType
>
> Check `Items In Discussion`_ to see what types could OtherType be, and
> what happens in each case.

(cf comments there)

But what about rules for precision promotion -- i.e., if you add
Decimal('1.1') and Decimal('0.05') I would expect the result to have the
more precise precision, and repr like "Decimal('1.15')"

>
>13. To support unary operators (``-, +, abs``).
>
>14. To support the built-in methods:
>
> - min, max
> - float, int, long
> - str, repr
> - hash
> - copy, deepcopy
> - bool (0 is false, otherwise true)
>
>15. To be immutable.
>
>
>Reference Implementation
>========================
>
>To be included later:
>
> - code
> - test code
> - documentation
>

I wonder about separation of base abstract class concerns from typical
subclassing and "context" and how this is tested/documented.

ISTM that a driving use case is currency stuff. OTOH, IWT you'd want to
keep the purely numeric stuff fairly clean.

BTW, are there left-right ordering rules that have to be adhered to strictly?
I have a hunch (a*b)*c could sometimes come out different from a*(b*c)
if rounding-to-specified-precision is imposed at every operation.

These are just some thoughts OTTOMH, so don't take too seriously ;-)


Regards,
Bengt Richter

Steve Williams

unread,
Nov 2, 2003, 12:00:53 AM11/2/03
to
John Roth wrote:
[snkip]

> Part of the reason why COBOL has the separate operators
> is that the *destination* of the operation specifies how the
> result is computed. You can't do that with intermediate
> results if you use expression notation.
>

Ah well, it's Saturday night, I've had a glass of wine, and so I post on
comp.lang.python.

Assert: Bob Bemer's PICTURE clause (supremely platform independent) is
the dreaded static data type.

You can write 1000 lines of COBOL arithmetic using + - * / and get
consistent results based on the PICTURE of the target and the ROUNDED
clause.

You can even sit down with the end user (she who is uninformed as to the
wonders of real analysis, but who holds your career in her hands) and
discuss and markup the program listing. Even if she doesn't know the
difference between / and //.

Moreover, most COBOL compilers accept any PICTURE clause as a source. Viz:

03 Amount Pic $$$,$$$,$$9.99

Compute Amount = Units * Unit-Cost

Compute Total-Amount = Total-Amount + Amount

Divide Days into Amount Giving Amounts-Per-Day Rounded

You want precision? Voila: Pic $,$$$,$$$,$$9.9999.

(Forgive me, computer scientists, for the use of the nasty verbs--that's
just COBOL).

Instead, we discuss separate operations for integers, rationals, reals,
complex, quaternions, octionions and 'money'--"To infinity and beyond."

I've written Python functions where I pass in a target picture along
with operands. But then it was on Saturday night, and I had a glass of
wine.


Batista, Facundo

unread,
Nov 4, 2003, 12:47:54 PM11/4/03
to Python-List (E-mail)
Alex Martelli wrote:

#- So you are proposing that, in this *ONE PLACE* out of ALL the huge
#- variety in the Python language,
#-
#- x = 1.1
#- ...
#- y = decimal(x)
#-
#- give a DIFFERENT result than
#-
#- y = decimal(1.1)
#-
#- ??? This is just _not in the cards_. Python's regularity,
#- uniformity,
#- and simplicity, are FAR more important than any one "special
#- case" can
#- possibly be. Or, as the Zen of Python puts it, "special cases aren't
#- special enough to break the rules".

I refuse to make something like this. Here I agree with Alex.

. Facundo

Batista, Facundo

unread,
Nov 4, 2003, 12:50:55 PM11/4/03
to Python-List (E-mail)

Ron Adam wrote:

#- >The Decimal data type should support the Python standard
#- functions and
#- >operations and must comply the decimal arithmetic ANSI
#- standard X3.274-1996.
#-
#- Is this the correct ANSI number?  I ended up at the following
#- websight.
#-
#- http://www.rexxla.org/Standards/standards.html


I took the ANSI number from the previous Decimal.py code.

Anyway, I didn't read the ANSI standard, just the General Decimal Arithmetic Specification, the Mike Cowlishaw's work at http://www2.hursley.ibm.com/decimal/.

.       Facundo





. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

ADVERTENCIA 

La información contenida en este mensaje y cualquier archivo anexo al mismo, son para uso exclusivo del destinatario y pueden contener información confidencial o propietaria, cuya divulgación es sancionada por la ley.

Si Ud. No es uno de los destinatarios consignados o la persona responsable de hacer llegar este mensaje a los destinatarios consignados, no está autorizado a divulgar, copiar, distribuir o retener información (o parte de ella) contenida en este mensaje. Por favor notifíquenos respondiendo al remitente, borre el mensaje original y borre las copias (impresas o grabadas en cualquier medio magnético) que pueda haber realizado del mismo.

Todas las opiniones contenidas en este mail son propias del autor del mensaje y no necesariamente coinciden con las de Telefónica Comunicaciones Personales S.A. o alguna empresa asociada.

Los mensajes electrónicos pueden ser alterados, motivo por el cual Telefónica Comunicaciones Personales S.A. no aceptará ninguna obligación cualquiera sea el resultante de este mensaje.

Muchas Gracias.

Batista, Facundo

unread,
Nov 4, 2003, 12:46:26 PM11/4/03
to Python-List (E-mail)
John Roth wrote:

#- As I said in the response to Alex, division is the one place where
#- fixed decimal gets into trouble. To repeat what I said to him,
#- I'd eliminate the division operators completely, and replace them
#- with a div(dividend, divisor, [resultplaces], [roundingpolicy])
#- operator.

Don't like it.

Why can't I do a / b (being a and b decimals)? Sure there're answers, but
Decimal is a numeric type and I *want* to divide in the standard way.

And remember that the result places is the precision and the rounding policy
is defined in the context, so those always are being taken in consideration.


#- > >> When passing floating point to the constructor, what
#- should happen?
#- > >>
#- > >> j. ``Decimal(1.1) == Decimal('1.1')``
#- > >> k. ``Decimal(1.1) ==
#- > >> Decimal('110000000000000008881784197001252...e-51')``
#- > >
#- > ...
#- > As I say, it's hard.
#-
#- Not that hard. It's not at all difficult to find where the
#- actual number
#- ends and where the fuzz begins. You can do it visually, and the
#- algorithms to do it are quite well known. That's how
#- printing libraries
#- handle the issue, after all.

But If I *really* want my number to be
Decimal('110000000000000008881784197001252...e-51'), why can't I write
Decimal(1.1)? Why should I expect Decimal to be "rounding" it?

Remember that I *know* that 1.1 is binary floating point, so I can predict
the result. It's not intuitive to a begginer, nut that's the way it is.

. Facundo

Batista, Facundo

unread,
Nov 4, 2003, 12:34:28 PM11/4/03
to Python-List (E-mail)
John Roth wrote:

#- Why is ANSI 274 significant? The reason I ask this is that this is
#- a ***floating point*** standard, and I don't think that we
#- particularly
#- care for decimal floating point.
#-
#- Floating point presumes limited precision. In other words,
#- if the actual
#- number (exclusive of the location of the decimal point) gets
#- too large,
#- the least significant part is ... thrown away. I don't want that.

If what you're saying is that, with a precision of 5...

45 --> 00045E+0
45321 --> 45321E+0
453211 --> 45321E+1

...you're right.

You may not like it, but that what it is: I'm following the General Decimal
Arithmetic Specification, see Mike Cowlishaw's work at
http://www2.hursley.ibm.com/decimal/.


#- The "other type" should be handled in the same way the decimal()
#- constructor would handle it.

¿Are you saying that if Decimal('45') is valid, Decimal('45') + '25' should
also be valid? Mmm... ugly, ugly.

#- > 1. The syntax should be ``Decimal(value)``.
#-
#- Lower case: "decimal(value, [scale factor])"

I thought that first letter in uppercase was good style for class names.

#- > 4. The Context must be omnipresent, meaning that changes
#- to it affects all
#- > the current and future Decimal instances.
#-
#- No. The context should be selectable for the particular
#- usage. That is,
#- it should be possible to have several different contexts in
#- play at one
#- time in an application.

The concept of context is a "configuration place" surrounding the instances.


What you propose?

- the configuration (precision, flags, etc) is on by-instance basis
- you have different contexts, and a group of instances with each
context.


#- > 9. To have different kinds of rounding; you can choose the
#- algorithm
#- through
#- > context:
#- > ...
#- I think this is simply too much. I'd rather have a round()
#- method that
#- takes a *small* number of standard options, and otherwise takes a
#- function to do the rounding.

Again, this is the General Decimal Arithmetic Specification.


. Facundo

Batista, Facundo

unread,
Nov 4, 2003, 12:40:37 PM11/4/03
to Python-List (E-mail)

Paul Moore wrote:

#- >> When passing floating point to the constructor, what
#- should happen?
#- >>
#- >>     j. ``Decimal(1.1) == Decimal('1.1')``
#- >>     k. ``Decimal(1.1) ==
#- >> Decimal('110000000000000008881784197001252...e-51')``
#- >

#- > Clearly, j is the correct answer. It's not all that hard
#- to do, either.
#-
#- No way. Consider:
#-
#- >>> 1.1
#- 1.1000000000000001
#- >>> 1.1==1.1000000000000001
#- True
#-
#- So what should Decimal(1.1000000000000001) evaluate to? It can't be
#- Decimal('1.1'), as that contradicts your statement that j "clearly"
#- applies. But it *also* can't be
#- Decimal('1.1000000000000001'), as then
#- we have the *same number* converting to two *different* Decimal
#- values.

This is really the problem. Option (j) seems to be correct but is not. Option (k) *is* correct, but not what you want to do, :(

Maybe the more natural solution is not to accept float (you always can write that *same* number as a string, and it'll be correct, and sure i'll be doing what you expect).



#- into :-) Having a special method, say Decimal.round_float(f, digits),
#- is probably OK, though...

Mmmm. Digits in the first meaning or the second? Should round or truncate?

Batista, Facundo

unread,
Nov 4, 2003, 12:16:05 PM11/4/03
to Python-List (E-mail)
Emile van Sebille wrote:

#- > 3. To exist a Context. The context represents the user-selectable
#- > parameters
#- > and rules which govern the results of arithmetic
#- operations. In the
#- > context the user defines:
#- >
#- > - what will happen with the exceptional conditions.
#- > - what precision will be used
#- > - what rounding method will be used
#- >


#- > 4. The Context must be omnipresent, meaning that changes
#- to it affects all
#- > the current and future Decimal instances.
#-

#- Does this imply then that there is unlimited precision being
#- maintained
#- under the covers?

No, why?

Under the cover you got:

- sign
- coefficient (just several decimal digits, fixed in quantity)
- exponent

. Facundo

Batista, Facundo

unread,
Nov 4, 2003, 12:35:46 PM11/4/03
to Python-List (E-mail)

Alex Martelli wrote:

#- There may be a niche for a Rational data type, but in MHO it
#- cannot take the
#- place of a limited-precision-decimal (fixed or float).  I
#- suggest you think
#- of a separate PEP to propose Rational (check the existing
#- and rejected ones
#- first, there may be some that are relevant) rather than
#- attacking this one.
#- ...
#- (Facundo, I think you should include these considerations in
#- the PEP to
#- explain why you're NOT going for rationals.)

OK.

John Roth

unread,
Nov 4, 2003, 1:57:01 PM11/4/03
to

"Batista, Facundo" <FBat...@uniFON.com.ar> wrote in message
news:mailman.425.1067967...@python.org...
John Roth wrote:

#- Why is ANSI 274 significant? The reason I ask this is that this is
#- a ***floating point*** standard, and I don't think that we
#- particularly
#- care for decimal floating point.
#-
#- Floating point presumes limited precision. In other words,
#- if the actual
#- number (exclusive of the location of the decimal point) gets
#- too large,
#- the least significant part is ... thrown away. I don't want that.

If what you're saying is that, with a precision of 5...

45 --> 00045E+0
45321 --> 45321E+0
453211 --> 45321E+1

...you're right.

You may not like it, but that what it is: I'm following the General Decimal
Arithmetic Specification, see Mike Cowlishaw's work at
http://www2.hursley.ibm.com/decimal/.

[John Roth]
If I understand his work, that's not actually true. I may not
be understanding what he's saying, though.


#- The "other type" should be handled in the same way the decimal()
#- constructor would handle it.

[Facundo Batista]


¿Are you saying that if Decimal('45') is valid, Decimal('45') + '25' should
also be valid? Mmm... ugly, ugly.

[John Roth]
I can see how what I said could be interpreted that way, but I certainly
didn't mean it for strings.


#- > 1. The syntax should be ``Decimal(value)``.
#-
#- Lower case: "decimal(value, [scale factor])"


[Facundo Batista]


I thought that first letter in uppercase was good style for class names.

[John Roth]
It is. I was thinking in terms of a type, not a class. All the builtin
types start with lower class names.

[pre-pep]


#- > 4. The Context must be omnipresent, meaning that changes
#- to it affects all
#- > the current and future Decimal instances.
#-

[John Roth]


#- No. The context should be selectable for the particular
#- usage. That is,
#- it should be possible to have several different contexts in
#- play at one
#- time in an application.


[Facundo Batista]


The concept of context is a "configuration place" surrounding the instances.


What you propose?

- the configuration (precision, flags, etc) is on by-instance basis
- you have different contexts, and a group of instances with each
context.

[John Roth]
More likely the second. My concern here is the usual one with
singletons and globals. Processing gets very messy when you
have to operate in several different modes or areas. See the
difficulties people get into with internationalization when they
have an application that has to operate in several different
jurisdictions at once, etc.

[Facundo Batista]


#- > 9. To have different kinds of rounding; you can choose the
#- algorithm
#- through
#- > context:
#- > ...

[John Roth]


#- I think this is simply too much. I'd rather have a round()
#- method that
#- takes a *small* number of standard options, and otherwise takes a
#- function to do the rounding.


[Facundo Batista]


Again, this is the General Decimal Arithmetic Specification.

[John Roth]
I didn't understand that at the time I made the remark.

However, I think I'll emphasize my viewpoint one last
time, with the understanding that I'm not going to defend
it, comment on it, or even reply to comments on it unless
they show some interesting (to me, at least) understanding
of the issue I'm bringing up.

I think that many parts of this are attempting to shoot
mosquitoes with an elephant gun. A lot of the general
specification can be understood as things that make
*hardware* implementation of decimal floating point
simpler.

Much of this is from the binary floating point standards,
and historically Python has not implemented most of
it. It's depended on whatever the C compiler and library
gave it for floating point, leading to massive inconsistencies
in the treatment of infinities and NaNs, for example.

Unless Intel is already planning on putting this in
a near future version of their commercial chips,
it's going to be years before a hardware
implementation is of any concern.

As far as standardization is concerned, what I
get from Collishaw's site is that there are at least
three threads: a decimal floating point package that
he describes as "the core arithmetic" which seems
to have been used quite successfully for over two
decades; IEEE 854-1987, which is a generalization of
IEEE 754 (floating point) and the ongoing
revision of IEEE 754 with will merge the two. There
is also an ANSI standard, ANSI X3.274-1996.
Eric Price's module (referenced from Collishaw's site)
seems to be based on the latter, which will be
superceeded by the IEEE 754 revision, whenever it
comes out.

So we're planning
on doing a software implementation of a *draft* standard,
including very complicated facilities that I most
respectfully think are going to be of marginal
utility.

So be it.

John Roth


. Facundo


Batista, Facundo

unread,
Nov 4, 2003, 1:08:36 PM11/4/03
to Python-List (E-mail)
Alex Martelli wrote:

#- The Decimal type discussed in this PEP is the arithmetic fundament
#- for Money. Facundo started out with the idea of a PEP for a type
#- named Money that mixed arithmetic, parsing and formatting concerns.

Are you sure? Because, for example with a precision of 9:

123 --> 123e0
1122334455667788 --> 112233445e7

Meaning that you lost the last digits. We're talking about billions here,
but an accounting person won't be happy with that.

I think that for Money with need a Fixed Point Decimal, but that's a whole
new problem.


#- He received ample and detailed feedback, on the basis of which he
#- (quite reasonably, IMHO) concluded that a Decimal type, based on
#- existing nondist/sandbox implementations that realized Cowlishaw's
#- ideas and work (as per the first URL above) would be the right
#- fundament for a Money type (which could either subclass or use it
#- and add parsing and formatting niceties as needed).

I thought Decimal was the answer for Money, but now I'm not so sure. In the
meantime, I read the code and the specification and commited myself to make
the PEP.


#- So, this Decimal type isn't "duplicating" anything: it's intended
#- to _supply_ the arithmetic features needed (inter alia) by money
#- computations.

Is still right that we're not duplicating efforts. If this will be the base
for Money, I don't know. But I'm sure it'll be useful for doing decimal
math.


#- > I can see some justification for a simple, straightforward,
#- > fixed decimal type that makes reasonable assumptions in
#- > corner cases, while still allowing the programmer a good
#- > deal of control if she wants to exercise it.
#-
#- I do not think Decimal is limited to either fixed or floating
#- point. The Hursley site is quite specific in claiming that
#- both can be supported in a single underlying type. Unbounded
#- precision is a different issue.

Didn't found that.

And I'm not quite sure that I understand what you mean with "unbounded
precision" (that you can not say with fixed or floating point). Could you
please explain that? thanks!

. Facundo


Batista, Facundo

unread,
Nov 4, 2003, 1:11:25 PM11/4/03
to Python-List (E-mail)
mwi...@the-wire.com wrote:

#- I admit that the implementation I'm playing with doesn't
#- implement __div__ or __truediv__ yet .. because I can't
#- figure out the right way.

That's the coolness of taking and implementing an well established standard,
;)

. Facundo

Tim Peters

unread,
Nov 4, 2003, 1:50:43 PM11/4/03
to Python-List (E-mail)
[Alex Martelli]

> #- The Decimal type discussed in this PEP is the arithmetic fundament
> #- for Money. Facundo started out with the idea of a PEP for a type
> #- named Money that mixed arithmetic, parsing and formatting concerns.
> Are you sure? Because, for example with a precision of 9:

[Facundo Batista]


> Are you sure? Because, for example with a precision of 9:
>
> 123 --> 123e0
> 1122334455667788 --> 112233445e7
>
> Meaning that you lost the last digits. We're talking about billions
> here, but an accounting person won't be happy with that.

Then they shouldn't use such a tiny precision; that's essentially IEEE
single precision. Default to a precision of, say, 100, and you can count
the number of electrons in the universe exactly. Since each penny contains
at least one electron ... <wink>.

> I think that for Money with need a Fixed Point Decimal, but that's a
> whole new problem.

You really don't. Cowlishaw's spec provides all you really need (wrt
arithmetical semantics) from a fixed point type, provided only that you ask
for enough precision in advance. A precision of 100 is absurdly large for
any conceivable accounting application. A precision of 20 would be way more
than enough to account for total world economic output, down to the penny,
since the beginning of time -- remember that the number of exactly
representable quantities grows exponentially with precision.

> ...


> And I'm not quite sure that I understand what you mean with "unbounded
> precision" (that you can not say with fixed or floating point). Could
> you please explain that? thanks!

Unbounded precision ("to the left" of the radix point) is what my old
FixedPoint.py class did/does: if you multiply two FixedPoint integers, the
result is exact, no matter how many decimal digits the exact product
requires. The IBM spec allows for setting precision, but once set results
are always rounded to that precision. IOW, under the IBM spec the precision
is an upper bound on the number of significant digits in a result (but is a
user-settable upper bound). In FixedPoint there is no upper bound. In
REXX, people wanting the effect of unbounded precision usually set DIGITS
(analogous to the spec's precision) to, e.g., 999999999. Not a good idea
for most divisions <wink>, but for + - and * you'd run out of memory before
a result ever got rounded.


Batista, Facundo

unread,
Nov 4, 2003, 1:26:35 PM11/4/03
to Python-List (E-mail)

bo...@oz.net wrote:

#- (First, thanks for the plain-text post ;-)

This is not my choice. I *always* send the mail as plain-text.

But sometimes (and I don't know specifically when) the mail server convert it to RTF and adds a warning ad, :(. The perils of working to a Exchange-Mail-Server kind of enterprise.


#-     I.e., '1.1' means something different from '1.1000',
#- even though hopefully
#-     Decimal('1.1')==Decimal('1.1000').

Decimal('1.1') == Decimal('1.1000') == Decimal(11) / Decimal(10)


#- >1. The syntax should be ``Decimal(value)``.

#-     Decimal(value, precision_info) when the value cannot be
#- presumed exact, IWT.
#- >
#- >2. The value could be of the type:
#- >
#- >       - another Decimal
#-           presume this is just a copy, unless precision info
#- is provided?

That's right.


#- >       - int or long
#-           assume no-fractional-bits precision?

Remember that the precision is in the context.


#-           even if there is an exponent? I.e., what does
#- '1.000e3' mean? Same as '1000' or
#-           '1000.000' ? What about '1.000e-2' ? Same as
#- '0.010' or as '0.01' ?

Decimal('1.000e3') == Decimal('1000') == Decimal('1000.000')

and

Decimal('1.000e-2') == Decimal('0.010') == Decimal('0.01')

as a human can tell! :)


#- Maybe there needs to be an abstract base class that you
#- _have_ to subclass and
#- specify all these things?

You just specify in the context what type of rounding you want. And that's all.


#- >       m = Decimal(...)
#- >       m == eval(repr(m))
#- Does that mean repr(m) will always look like
#- "Decimal('<string literal>')" ?

Still don't know.


#- But what about rules for precision promotion -- i.e., if you add
#- Decimal('1.1') and Decimal('0.05') I would expect the result
#- to have the
#- more precise precision, and repr like "Decimal('1.15')"

Remember the precision is in the context, not in each instance.


#- I have a hunch (a*b)*c could sometimes come out different
#- from a*(b*c)
#- if rounding-to-specified-precision is imposed at every operation.

This can happen, as with all the roundings data types.

Anyway, you always can set the "rounding" trap in the context to raise an exception...

.       Facundo

Aahz

unread,
Nov 4, 2003, 3:07:24 PM11/4/03
to
In article <mailman.295.1067625...@python.org>,

Batista, Facundo <FBat...@uniFON.com.ar> wrote:
>
> if otherType is an int or long:
>
> a. an exception is raised
> b. otherType is converted to Decimal
> c. Decimal is converted to int or long (with ``int()`` or
>``long()``)

otherType is converted to Decimal unless precision in the current
Context would be exceeded; in that case you raise ValueError.

> if otherType is a float:
>
> d. an exception is raised

> e. otherType is converted to Decimal (rounding? see next item in
> discussion)

> f. Decimal is converted to float (with ``float()``)

Raise an exception. Because of the precision issues in floating point,
conversion to Decimal must always be explicit.

> if otherType is a string:
>
> g. an exception is raised

> h. otherType is converted to Decimal
> i. Decimal is converted to string (bizarre, huh?)

Exception is raised, just as with all other uses of strings and numbers.

>When passing floating point to the constructor, what should happen?


>
> j. ``Decimal(1.1) == Decimal('1.1')``

> k. ``Decimal(1.1) ==
>Decimal('110000000000000008881784197001252...e-51')``

That's tough. I'm inclined toward requiring an explicit conversion
through a function call that isn't the Decimal constructor, but ease of
use is also a factor.

>2. The value could be of the type:
>

> - another Decimal
> - int or long
> - float
> - string

Also a tuple of int/longs.

>4. The Context must be omnipresent, meaning that changes to it affects all


> the current and future Decimal instances.

Here's the tricky part (and where I abandoned work): the Context must be
thread-local. Also, Context applies only to operations, not to Decimal
instances; changing the Context does not affect existing instances if
there are no operations on them.

>15. To be immutable.

This is why the Context can't affect existing instances. ;-)
--
Aahz (aa...@pythoncraft.com) <*> http://www.pythoncraft.com/

"It is easier to optimize correct code than to correct optimized code."
--Bill Harlan

Batista, Facundo

unread,
Nov 4, 2003, 3:41:45 PM11/4/03
to Python-List (E-mail)
Tim Peters wrote:

#- [Facundo Batista]
#- > Are you sure? Because, for example with a precision of 9:
#- >
#- > 123 --> 123e0
#- > 1122334455667788 --> 112233445e7
#- >
#- > Meaning that you lost the last digits. We're talking about billions
#- > here, but an accounting person won't be happy with that.
#-
#- Then they shouldn't use such a tiny precision; that's
#- essentially IEEE
#- single precision. Default to a precision of, say, 100, and
#- you can count
#- the number of electrons in the universe exactly. Since each
#- penny contains
#- at least one electron ... <wink>.

Nice picture, :). But in Money you *want* to get rounded in the decimal
places.

Example of decimal with precision = 100:

1122334455667788 --> 1122334455667788
1122334455.667788 --> 1122334455.667788

But in Money, you may want to get rounded to two decimals:

1122334455.667788 --> 1122334455.67


#- > ...
#- > And I'm not quite sure that I understand what you mean
#- with "unbounded
#- > precision" (that you can not say with fixed or floating
#- point). Could
#- > you please explain that? thanks!
#-
#- Unbounded precision ("to the left" of the radix point) is what my old
#- FixedPoint.py class did/does: if you multiply two
#- FixedPoint integers, the

So, unbounded precision and fixed point are the same? (I always assumed
that)

. Facundo

Batista, Facundo

unread,
Nov 4, 2003, 3:57:26 PM11/4/03
to Python-List (E-mail)
John Roth wrote:

#- I can see how what I said could be interpreted that way, but
#- I certainly
#- didn't mean it for strings.

If you understand one thing and I understand another, one of both of us is
wrong, :p. Who? In my last example, what do you think that happens?


#- It is. I was thinking in terms of a type, not a class. All
#- the builtin
#- types start with lower class names.

OK, so it stays uppercase (as long it's a class).


#- What you propose?
#-
#- - the configuration (precision, flags, etc) is on by-instance basis
#- - you have different contexts, and a group of instances with each
#- context.
#-
#- [John Roth]
#- More likely the second. My concern here is the usual one with
#- singletons and globals. Processing gets very messy when you
#- have to operate in several different modes or areas. See the
#- difficulties people get into with internationalization when they
#- have an application that has to operate in several different
#- jurisdictions at once, etc.

In another mail, Aahz explains (even to me) that the idea is to have a
"context per thread". So, all the instances of a thread belongs to a
context, and you can change a context in thread A (and the behaviour of the
instances of that thread) without changing nothing on the thread B.

So, I think your proposal has future, as long I could finish the Aahz work,
;)


#- So we're planning
#- on doing a software implementation of a *draft* standard,
#- including very complicated facilities that I most
#- respectfully think are going to be of marginal
#- utility.

But is that or is making a Money data type and reinventing the wheel for all
its arithmetic behaviour.

I can't assure you if somebody ever will use the "not a number" capabilities
of Decimal (I think a lot of people will). But that's the specification, :p

. Facundo

Batista, Facundo

unread,
Nov 4, 2003, 4:04:21 PM11/4/03
to Python-List (E-mail)
Aahz wrote:

#- > if otherType is an int or long:
#- >
#- > a. an exception is raised
#- > b. otherType is converted to Decimal
#- > c. Decimal is converted to int or long (with ``int()`` or
#- >``long()``)
#-
#- otherType is converted to Decimal unless precision in the current
#- Context would be exceeded; in that case you raise ValueError.

Like it!


#- > if otherType is a float:
#- >
#- > d. an exception is raised
#- > e. otherType is converted to Decimal (rounding? see
#- next item in
#- > discussion)
#- > f. Decimal is converted to float (with ``float()``)
#-
#- Raise an exception. Because of the precision issues in
#- floating point,
#- conversion to Decimal must always be explicit.

Seems to be the best solution, according to that we can't found a "common
sense" behaviour.


#- > if otherType is a string:
#- >
#- > g. an exception is raised
#- > h. otherType is converted to Decimal
#- > i. Decimal is converted to string (bizarre, huh?)
#-
#- Exception is raised, just as with all other uses of strings
#- and numbers.

We all agree here.

#- >When passing floating point to the constructor, what should happen?
#- >
#- > j. ``Decimal(1.1) == Decimal('1.1')``


#- > k. ``Decimal(1.1) ==
#- >Decimal('110000000000000008881784197001252...e-51')``
#-

#- That's tough. I'm inclined toward requiring an explicit conversion
#- through a function call that isn't the Decimal constructor,
#- but ease of
#- use is also a factor.

We can have a Decimal.fromFloat(...), but with behaviour j or k?

#- >2. The value could be of the type:


#- >
#- > - another Decimal

#- > - int or long

#- > - float
#- > - string
#-
#- Also a tuple of int/longs.

I didn't include that in the prePEP because it seems that the actual
implementation support this just to dec == Decimal(repr(dec)), for every
Decimal as dec.

Has any other advantage?


#- >4. The Context must be omnipresent, meaning that changes to
#- it affects all
#- > the current and future Decimal instances.
#-
#- Here's the tricky part (and where I abandoned work): the
#- Context must be
#- thread-local. Also, Context applies only to operations, not
#- to Decimal
#- instances; changing the Context does not affect existing instances if
#- there are no operations on them.

OK. I'll change the item 4.


#-
#- >15. To be immutable.
#-
#- This is why the Context can't affect existing instances. ;-)

You're right. Again. :)

. Facundo

John Roth

unread,
Nov 4, 2003, 5:40:46 PM11/4/03
to

"Batista, Facundo" <FBat...@uniFON.com.ar> wrote in message
news:mailman.445.1067979...@python.org...

> John Roth wrote:
>
> #- I can see how what I said could be interpreted that way, but
> #- I certainly
> #- didn't mean it for strings.
>
> If you understand one thing and I understand another, one of both of us is
> wrong, :p. Who? In my last example, what do you think that happens?

I usually think that determining who was right or wrong is a waste
of effort that could be better spent on finding a mutually satisfactory
solution.

I don't remember the exact example, so I'm guessing that the
underlying issue is the one of not having an unambiguous decimal
literal. As currently defined, Decimal(1.1) and Decimal("1.1")
go through the same process: 1.1 first gets converted to a float,
and then converted a second time to a Decimal. I'd rather not
have even the potential for confusion.

Part of the reason for wanting a type (rather than a class)
is that it's much easier to justify a specific literal for a
built in type. And it would not be that hard to do either,
1.1D is quite natural, and not all that easy to mistake for
a float (the D isn't part of the float syntax in Python,
although it is in other languages.)

> #- It is. I was thinking in terms of a type, not a class. All
> #- the builtin
> #- types start with lower class names.
>
> OK, so it stays uppercase (as long it's a class).
>
>
> #- What you propose?
> #-
> #- - the configuration (precision, flags, etc) is on by-instance basis
> #- - you have different contexts, and a group of instances with each
> #- context.
> #-
> #- [John Roth]
> #- More likely the second. My concern here is the usual one with
> #- singletons and globals. Processing gets very messy when you
> #- have to operate in several different modes or areas. See the
> #- difficulties people get into with internationalization when they
> #- have an application that has to operate in several different
> #- jurisdictions at once, etc.
>
> In another mail, Aahz explains (even to me) that the idea is to have a
> "context per thread". So, all the instances of a thread belongs to a
> context, and you can change a context in thread A (and the behaviour of
the
> instances of that thread) without changing nothing on the thread B.

I saw his comment. I believe the reason why it needs to be thread local
has more to do with data integrity than a desire to provide for multiple
contexts, but if that's all that's possible, then that's what we'll get.

> So, I think your proposal has future, as long I could finish the Aahz
work,
> ;)
>
>
> #- So we're planning
> #- on doing a software implementation of a *draft* standard,
> #- including very complicated facilities that I most
> #- respectfully think are going to be of marginal
> #- utility.
>
> But is that or is making a Money data type and reinventing the wheel for
all
> its arithmetic behaviour.
>
> I can't assure you if somebody ever will use the "not a number"
capabilities
> of Decimal (I think a lot of people will). But that's the specification,
:p

The trouble is that we're trying to get two rather different things out of
one implementation.

The reason I say that they're rather different is that floating point and
fixed point have very different application domains. Floating point is for
continuous measurements of the kind that occur in the natural world,
fixed point is for counting discrete entities, like coins. Floating decimal
is better than floating binary in one respect, but it's still forcing two
different
things together.

My personal opinion in the matter is that setting the precision high
enough so that you won't get into trouble is a hack, and it's a dangerous
hack because the amount of precision needed isn't directly related to the
data you're processing; it's something that came out of an analysis,
probably by someone else under some other circumstances. Given
a software implementation, there's a performance advantage to setting it as
low
as possible, which immediately puts things at risk if your data
changes.

Fixed precision is also counter to the infinite precision we're moving
toward with the integers; and integers are a more comfortable metaphor
for money than floats.

The natural implementation for fixed decimal is much simpler
than for floating decimal. And I frankly don't think that floating
decimal is going to get that much use outside of an accounting
context, given that I think it's going to be a *lot* slower than
built-in binary floating point. Especially if PyPy succeeds in
their dream of creating a JIT for Python.

John Roth
>
> . Facundo
>


Ron Adam

unread,
Nov 4, 2003, 6:54:47 PM11/4/03
to
On Tue, 4 Nov 2003 13:50:43 -0500, "Tim Peters" <tim...@comcast.net>
wrote:

>Then they shouldn't use such a tiny precision; that's essentially IEEE
>single precision. Default to a precision of, say, 100, and you can count
>the number of electrons in the universe exactly. Since each penny contains
>at least one electron ... <wink>.


Since the Federal Reserve is moving to an all electronic system, it is
now possible for the amount of currency to exceed any and all
resources to represent money in a physical form.

And so we can finally prove that we live in an inflated universe.


_Ron ;-)


Bengt Richter

unread,
Nov 4, 2003, 7:09:46 PM11/4/03
to
On Tue, 4 Nov 2003 15:26:35 -0300, "Batista, Facundo" <FBat...@uniFON.com.ar> wrote:
[...]
>
>#- I.e., '1.1' means something different from '1.1000',=20
>#- even though hopefully
>#- Decimal('1.1')=3D=3DDecimal('1.1000').
>
>Decimal('1.1') =3D=3D Decimal('1.1000') =3D=3D Decimal(11) / =
>Decimal(10)
Yes, but what about
Decimal('1.1')/Decimal('3') =?=> Decimal('0.4')
vs
Decimal('1.1000')/Decimal('3') =?=> Decimal('0.3667')
vs
(Decimal(11)/Decimal(10))/Decimal('3') =?=> Decimal('0.4')

so e.g.,
Decimal('1.1')/Decimal('3') !(?)= Decimal('1.1000')/Decimal('3')

Or is the "Context" thing defining what all the "Decimal" constructions above do,
and extra zeroes in a literal may be ignored?

(If so, how does a "Decimal" know what "Context" to refer to if you have more than
one "Context" going? -- IOW why wouldn't you take Decimal+Context => Decimal subclass,
and let the sublass instance find its "Context" info by way of its inheritance instead
of some other Decimal<->Context association?

I guess I am missing the "Context" info, sorry. Where is its relation to Decimal defined?

>
>
>#- >1. The syntax should be ``Decimal(value)``.

>#- Decimal(value, precision_info) when the value cannot be=20


>#- presumed exact, IWT.
>#- >
>#- >2. The value could be of the type:
>#- >
>#- > - another Decimal

>#- presume this is just a copy, unless precision info=20


>#- is provided?
>
>That's right.
>
>
>#- > - int or long
>#- assume no-fractional-bits precision?
>
>Remember that the precision is in the context.

Sorry, how is context defined, so as to be accessed by the Decimal constructor?
Or is there a metaclass that modifies Decimal with custom context?

>
>
>#- even if there is an exponent? I.e., what does=20


>#- '1.000e3' mean? Same as '1000' or

>#- '1000.000' ? What about '1.000e-2' ? Same as=20


>#- '0.010' or as '0.01' ?
>

>Decimal('1.000e3') =3D=3D Decimal('1000') =3D=3D Decimal('1000.000')=20
>
>and
>
>Decimal('1.000e-2') =3D=3D Decimal('0.010') =3D=3D Decimal('0.01')=20


>
>as a human can tell! :)

Well, I meant same in a broader sense, as in will it act the same? E.g., will
the result of dividing them each by Decimal('3') have the same (equal) results?
If not, then they may be equal before dividing, but they're not the same.

>
>
>#- Maybe there needs to be an abstract base class that you=20


>#- _have_ to subclass and
>#- specify all these things?
>

>You just specify in the context what type of rounding you want. And =
>that's
>all.
I guess I need a pointer to the context definition. I didn't wind up with a clear concept.
I probably was falling asleep or something. I guess I should re-read ;-/
>
>
>#- > m =3D Decimal(...)
>#- > m =3D=3D eval(repr(m))
>#- Does that mean repr(m) will always look like=20


>#- "Decimal('<string literal>')" ?
>
>Still don't know.

Ok, fair enough ;-)

>
>
>#- But what about rules for precision promotion -- i.e., if you add

>#- Decimal('1.1') and Decimal('0.05') I would expect the result=20


>#- to have the
>#- more precise precision, and repr like "Decimal('1.15')"
>

>Remember the precision is in the context, not in each instance.=20
Again, how does Decimal find whatever context it should get precision from?

>
>
>#- I have a hunch (a*b)*c could sometimes come out different=20


>#- from a*(b*c)
>#- if rounding-to-specified-precision is imposed at every operation.
>
>This can happen, as with all the roundings data types.
>

Isn't this a fairly subtle thing to expect programmers to realize, who you will
be trying to make things easy for with a Money class based on this stuff?

Of course, you could use this stuff at very high precision, and then implement
a class with Currency properties that would appear to enforce precision rounding
only on assignment, and appear to evaluate rhs expressions with (for practical purposes)
infinite precision. IMO that might be easier to think about. Right hand side expressions
would seem continuously mathematically accurate, and assignments would choose the discrete
quantized values. Then rules would be on theoretical values mapped at controlled times, and
the problem would be making sure that enough precision was carried to guarantee
as-if-infinite-precision rounding results (and presumably raising an exception if that
broke down). This sounds like interval math in disguise kind of, but maybe it's not that
bad in practical contexts?

>Anyway, you always can set the "rounding" trap in the context to raise =
>an
>exception...
>
Assuming you get a call to a handler from the middle of an expression evaluation,
what would you do? Seems like subtle programming decisions either to document or
pass the buck on in an EZMoney module built on this ;-)

Regards,
Bengt Richter

Tim Peters

unread,
Nov 4, 2003, 7:57:31 PM11/4/03
to Python-List (E-mail)
[Batista, Facundo]

> Nice picture, :). But in Money you *want* to get rounded in the
> decimal places.
>
> Example of decimal with precision = 100:
>
> 1122334455667788 --> 1122334455667788
> 1122334455.667788 --> 1122334455.667788
>
> But in Money, you may want to get rounded to two decimals:
>
> 1122334455.667788 --> 1122334455.67

What are these example of? Literals? If you don't want 40 digits after the
decimal point in a literal, don't write 40 digits after the decimal point in
a literal. Or, if you have to, use the spec's quantize() function to round
to the number of fractional decimal digits you want. After that, + and -
take care of themselves (they neither increase nor decrease the number of
decimal digits after the radix point, and IBM's arithmetic *remembers*
that -- e.g., 3.12 - 1.12 is 2.00 in that scheme, not 2 or 2.0 or 2.000).
You do need to explicitly round (if desired) after * and /. Because of the
high value attached to 100% conformance to imposed standards in accounting
apps, I think it's a good thing that you're forced to round explicitly when
the result of a calculation can't be represented exactly in the mandated
format -- those are exactly the places where your app will fail to meet the
requirements, so it's good to be aware of them.

> ...


> So, unbounded precision and fixed point are the same? (I always
> assumed that)

It depends on who you ask, but most people would probably say "no". "Fixed
point" has no fixed meaning -- it's been applied to a plethora of schemes
over the decades, and each meaning has its own gang of frightened defenders
<0.9 wink>. My FixedPoint class provided an historically peculiar meaning
(fixed # of digits after the decimal point, unbounded # of digits before
it), but one I found useful at the time. Most meanings of "fixed point"
have in mind a fixed number of digits both before and after the decimal
point, and rarely the same number of digits before as after. Variants in
all areas are rampant.

For example, for multiplication Java's BigDecimal class gives a number of
digits after the decimal point in the product equal to the sum of the number
of digits after the decimal points in both inputs; in the same case,
FixedPoint rounds the infinitely-precise result to the larger of the number
of digits after the decimal point across both inputs.

Both schemes are crazy for some apps. For example, if there's 8.875% tax on
a $1.01 item,

1.08875 * 1.01 = 1.0996375 # BigDecimal
= 1.09964 # FixedPoint

are both probably useless as-is. Under the IBM spec, you multiply and then
call quantize with a second argument of (for example) 0.01. Assuming
banker's rounding, that gives the 1.10 you're probably seeking. Or maybe
that's not what you want -- maybe your municipality wants the excess
truncated. They're both equally easy (and equally difficult <wink>) under
the IBM spec. What doesn't work is hoping that some class will do rounding
correctly in all cases by magic.


Emile van Sebille

unread,
Nov 5, 2003, 11:27:28 AM11/5/03
to
Batista, Facundo:
> Emile van Sebille wrote:
> #- Batista, Facundo:

> #- > 4. The Context must be omnipresent, meaning that changes
> #- to it affects all
> #- > the current and future Decimal instances.
> #-
> #- Does this imply then that there is unlimited precision being
> #- maintained
> #- under the covers?
>
> No, why?
>
> Under the cover you got:
>
> - sign
> - coefficient (just several decimal digits, fixed in quantity)
> - exponent
>

So, say I calculate gross margin percentage as (S-C)/S and that yields a
repeating post-decimal result (say (7-5)/7 or 2/7) and I save this result as
a Decimal. How will the coefficient be set such that for future calculations
with arbitrary precision I'd get the right result? This is what I
understood when you said that changes to Context would affect current and
future Decimal instances.

What I'm hoping will result from your work is a class/type that combines the
unbounded limits of longs with post decimal precision presentable to a
selectable number of post-decimal places.

Tim says it right on the pre-decimal point side:


> Unbounded precision ("to the left" of the radix point) is what my

> old FixedPoint.py class did/does: if you multiply two FixedPoint


> integers, the result is exact, no matter how many decimal digits the
> exact product requires.

...and Bengt Richter on the post-decimal point side:


> Of course, you could use this stuff at very high precision,
> and then implement a class with Currency properties that
> would appear to enforce precision rounding only on
> assignment, and appear to evaluate rhs expressions with
> (for practical purposes) infinite precision. IMO that might
> be easier to think about. Right hand side expressions would
> seem continuously mathematically accurate, and assignments
> would choose the discrete quantized values.

This seems a very practical compromise. ISTM that if your Decimal
guaranteed presentation accuracy out to 14 post-decimal positions by always
maintaining internal accuracy out to 20 (or pick other bounds you're
comfortable with), that we'd have something usable for monetary
applications. The specifics of rounding rules and such come later.

The Context still needs to be defined, but Aahz says:
> Context applies only to operations, not to Decimal instances;


> changing the Context does not affect existing instances if

> there are no operations on them.

...from which I infer that Context exists in part to limit/round calculated
results. Even if it were possible for me, as a user of Decimal, to set
Context appropriately to achieve these ends, what if I use a library that
also changes Context? The integrity of my calculations may be impacted.

I'd prefer to have the results of calculations accurate to known bounds that
exceed presentation and legal requirements. The rest is defined by the
specs of the job at hand.

Hoping you pull this off!

--

Emile van Sebille
em...@fenx.com


Batista, Facundo

unread,
Nov 5, 2003, 12:46:35 PM11/5/03
to Python-List (E-mail)

John Roth wrote:

#- literal. As currently defined, Decimal(1.1) and Decimal("1.1")
#- go through the same process: 1.1 first gets converted to a float,
#- and then converted a second time to a Decimal. I'd rather not
#- have even the potential for confusion.

Where's that defined?

Decimal(1.1) != Decimal("1.1") unless you choose an "being discused" option (j).


#- Part of the reason for wanting a type (rather than a class)
#- is that it's much easier to justify a specific literal for a
#- built in type. And it would not be that hard to do either,
#- 1.1D is quite natural, and not all that easy to mistake for
#- a float (the D isn't part of the float syntax in Python,
#- although it is in other languages.)

I think that this first must get into the standard library. Then we all can discuss if goes builtin.


#- My personal opinion in the matter is that setting the precision high
#- enough so that you won't get into trouble is a hack, and
#- it's a dangerous
#- hack because the amount of precision needed isn't directly
#- related to the
#- data you're processing; it's something that came out of an analysis,
#- probably by someone else under some other circumstances. Given
#- a software implementation, there's a performance advantage
#- to setting it as
#- low
#- as possible, which immediately puts things at risk if your data
#- changes.

This is why I'm still unsure of using Decimal as a superclass for Money.

Batista, Facundo

unread,
Nov 5, 2003, 12:56:45 PM11/5/03
to Python-List (E-mail)
bo...@oz.net

#- so e.g.,
#- Decimal('1.1')/Decimal('3') !(?)= Decimal('1.1000')/Decimal('3')

They are equal.


#- Or is the "Context" thing defining what all the "Decimal"
#- constructions above do,
#- and extra zeroes in a literal may be ignored?

Why won't extra zeroes, at the right of the decimal, be ignored?


#- (If so, how does a "Decimal" know what "Context" to refer to
#- if you have more than
#- one "Context" going? -- IOW why wouldn't you take
#- Decimal+Context => Decimal subclass,
#- and let the sublass instance find its "Context" info by way
#- of its inheritance instead
#- of some other Decimal<->Context association?

The Decimal has a context that dependes of its thread.

Each time it's about to do an operation, uses that context.


#- >Remember that the precision is in the context.
#- Sorry, how is context defined, so as to be accessed by the
#- Decimal constructor?
#- Or is there a metaclass that modifies Decimal with custom context?

The context don't get attached to the decimal instance when it's built. The
context is something that "floats around" in each thread. And if that
instance is in that thread, that instance will use that context.


#- I guess I need a pointer to the context definition. I didn't
#- wind up with a clear concept.
#- I probably was falling asleep or something. I guess I should
#- re-read ;-/

No. I didn't explained it at all in the prePEP. It's all in the Cowlishaw's
work.

. Facundo

Aahz

unread,
Nov 5, 2003, 1:55:09 PM11/5/03
to
In article <bob8a4$1bmvct$1...@ID-11957.news.uni-berlin.de>,

Emile van Sebille <em...@fenx.com> wrote:
>
>The Context still needs to be defined, but Aahz says:
>> Context applies only to operations, not to Decimal instances;
>> changing the Context does not affect existing instances if
>> there are no operations on them.
>
>...from which I infer that Context exists in part to limit/round
>calculated results. Even if it were possible for me, as a user of
>Decimal, to set Context appropriately to achieve these ends, what
>if I use a library that also changes Context? The integrity of my
>calculations may be impacted.

That's correct. There needs to be a social convention that libraries
intended for use by other people *CANNOT* muck with Context, and if they
do so for their own internal calculations, they must save and restore
Context. You can probably find additional information about this in
Cowlishaw.

Jp Calderone

unread,
Nov 5, 2003, 6:25:28 PM11/5/03
to pytho...@python.org
On Wed, Nov 05, 2003 at 01:55:09PM -0500, Aahz wrote:
> In article <bob8a4$1bmvct$1...@ID-11957.news.uni-berlin.de>,
> Emile van Sebille <em...@fenx.com> wrote:
> >
> >The Context still needs to be defined, but Aahz says:
> >> Context applies only to operations, not to Decimal instances;
> >> changing the Context does not affect existing instances if
> >> there are no operations on them.
> >
> >...from which I infer that Context exists in part to limit/round
> >calculated results. Even if it were possible for me, as a user of
> >Decimal, to set Context appropriately to achieve these ends, what
> >if I use a library that also changes Context? The integrity of my
> >calculations may be impacted.
>
> That's correct. There needs to be a social convention that libraries
> intended for use by other people *CANNOT* muck with Context, and if they
> do so for their own internal calculations, they must save and restore
> Context. You can probably find additional information about this in
> Cowlishaw.

Enter the context stack.

Jp


Alex Martelli

unread,
Nov 6, 2003, 3:22:15 AM11/6/03
to
Tim Peters wrote:
...

> You do need to explicitly round (if desired) after * and /. Because of
> the high value attached to 100% conformance to imposed standards in
> accounting apps, I think it's a good thing that you're forced to round
> explicitly when the result of a calculation can't be represented exactly
> in the mandated format -- those are exactly the places where your app will
> fail to meet the requirements, so it's good to be aware of them.

European Union directives (adopted as laws by member states) mandate
the rounding procedure to be used in computations involving Euros (round
to closest Eurocent, always round up on half-Eurocent results); they very
explicitly mention that this may give a 1-Eurocent discrepancy compared
to "exact" arithmetic, and give examples; they establish that such a 1-cent
discrepancy that comes from following exactly the prescribed rules is NOT
legal cause for any lawsuit whatsoever; they earnestly recommend that all
computing equipment and programs follow these same rules to avoid the huge
headaches that would result in trying to reconcile accounts otherwise.

Thus, for most accounting programs intended to run within the EU (not just
in Euros: these provisions also apply to the other non-Euro currencies, as
far as EU law is concerned), I do NOT think it would be a good thing for
the programmer to have to remember to round explicitly -- the legal mandate
is about rounding rules and it's quite easy to avoid the "fail to meet the
requirements", as they seem designed to be easy to meet.

Whether Decimal itself allows an optional rounding-policy (including of
course "no rounding" as a possibility) is one issue (I guess that might
violate some ANSI or IEEE standard...?) but I most surely do want to be
able to use such policies in whatever arithmetic type underlies Money --
so I hope Decimal is at least designed so that _subclasses_ can easily
provide such customized rounding (e.g., feature-testing for a __round__
specialmethod, not defined in the baseclass Decimal if need be, but
offering the needed hook for subclasses to add the functionality).


Alex

Emile van Sebille

unread,
Nov 6, 2003, 9:35:39 AM11/6/03
to
Alex Martelli:

> Thus, for most accounting programs intended to run within the EU
(not just
> in Euros: these provisions also apply to the other non-Euro
currencies, as
> far as EU law is concerned), I do NOT think it would be a good thing
for
> the programmer to have to remember to round explicitly -- the legal
mandate
> is about rounding rules and it's quite easy to avoid the "fail to
meet the
> requirements", as they seem designed to be easy to meet.
>

These rules must apply to the subset of accounting situations that
involve exchange of funds. However, pricing and costing of some items
regularly require a number of post-decimals beyond the '3 then round'
as you say is mandated. An encforced rounding policy provided by a
Money subclass would require the programmer to invent work arounds for
these situations. EIBTI (Cool! I just learned what that means.)

Aahz

unread,
Nov 6, 2003, 10:16:54 AM11/6/03
to
In article <Xwnqb.422473$R32.14...@news2.tin.it>,
Alex Martelli <al...@aleax.it> wrote:

>Tim Peters wrote:
>>
>> You do need to explicitly round (if desired) after * and /. Because of
>> the high value attached to 100% conformance to imposed standards in
>> accounting apps, I think it's a good thing that you're forced to round
>> explicitly when the result of a calculation can't be represented exactly
>> in the mandated format -- those are exactly the places where your app will
>> fail to meet the requirements, so it's good to be aware of them.
>
>European Union directives (adopted as laws by member states)
>mandate the rounding procedure to be used in computations involving
>Euros (round to closest Eurocent, always round up on half-Eurocent
>results); they very explicitly mention that this may give a 1-Eurocent
>discrepancy compared to "exact" arithmetic, and give examples; they
>establish that such a 1-cent discrepancy that comes from following
>exactly the prescribed rules is NOT legal cause for any lawsuit
>whatsoever; they earnestly recommend that all computing equipment and
>programs follow these same rules to avoid the huge headaches that would
>result in trying to reconcile accounts otherwise.

Yeah, but *what* do you round? If you purchase five items in a store,
does tax get applied to each item individually or to the total? There
are lots of similar cases, so rounding must still be at least partially
explicit.

>Whether Decimal itself allows an optional rounding-policy (including
>of course "no rounding" as a possibility) is one issue (I guess that
>might violate some ANSI or IEEE standard...?) but I most surely do want
>to be able to use such policies in whatever arithmetic type underlies
>Money -- so I hope Decimal is at least designed so that _subclasses_
>can easily provide such customized rounding (e.g., feature-testing
>for a __round__ specialmethod, not defined in the baseclass Decimal
>if need be, but offering the needed hook for subclasses to add the
>functionality).

Once again, I suggest that you read Cowlishaw.

Aahz

unread,
Nov 6, 2003, 10:18:52 AM11/6/03
to
In article <mailman.475.1068074...@python.org>,

Well, sure. And it won't be hard to add given that Decimal will already
need to track thread-specific Context. But modules changing Context
will still need to explicitly push and pop Context because usually you
will just want to replace the current Context.

Tim Peters

unread,
Nov 6, 2003, 10:43:52 AM11/6/03
to pytho...@python.org
[Alex Martelli]

> European Union directives (adopted as laws by member states) mandate
> the rounding procedure to be used in computations involving Euros
> (round to closest Eurocent, always round up on half-Eurocent
> results); they very explicitly mention that this may give a
> 1-Eurocent discrepancy compared to "exact" arithmetic, and give
> examples; they establish that such a 1-cent discrepancy that comes
> from following exactly the prescribed rules is NOT legal cause for
> any lawsuit whatsoever; they earnestly recommend that all computing
> equipment and programs follow these same rules to avoid the huge
> headaches that would result in trying to reconcile accounts
> otherwise.
>
> Thus, for most accounting programs intended to run within the EU (not
> just in Euros: these provisions also apply to the other non-Euro
> currencies, as far as EU law is concerned), I do NOT think it would
> be a good thing for the programmer to have to remember to round
> explicitly -- the legal mandate is about rounding rules and it's
> quite easy to avoid the "fail to meet the requirements", as they seem
> designed to be easy to meet.

I'd be astonished if the rules were consistent enough so that a type
implementing a fixed number of decimal digits "after the decimal point"
would be of real use for non-experts ... OK, here from:

http://www.eubusiness.com/emu/retail8.htm

The conversion of prices into euros will require the use of a six-
significant-digit conversion rate (six digits disregarding initial
zeros)
which should not be rounded or truncated. This rate will be irrevocably
fixed on 1 January 1999 and defined in the form of one euro expressed in
national currencies.

So that part mandates a *floating* point input (not meaning binary floating
point, but "6 digits disregarding initial zeros" is the essence of floating
point -- the total number of digits isn't fixed, nor is the # of digits
after the decimal point fixed, just the # of *significant* digits).

To convert from national currencies to the euro, one has to divide by
the conversion rate. To convert from the euro to the national currency,
one has to multiply by the conversion rate. The use of inverse rates is
forbidden.

Neutral.

To convert from one national currency to another, amounts must be first
converted into euros and then into the second national currency. The
euro amount must be rounded to three decimal places. The national
currency should then be rounded to two decimals.

So no single fixed-point discipline can suffice: in one direction they want
rounding to 3 digits after the decimal point, in the other to 2 digits, and
one of the inputs is a floating-point value with no fixed number of digits
after the decimal point.

This is all very easily done with IBM's proposed arithmetic, but it requires
the programmer to specify explicitly how many places after the decimal point
they want after each * and / operation. Since that value changes in
arbitrarily (from the POV of arithmetic semantics) mandated ways, no class
can guess what's required from step to step.

Again, this isn't an issue for + or -: if x and y have the same number of
digits after the decimal point, then x+y and x-y are exact, and inherit that
same number of fractional digits (provided only the user hasn't specified a
suicidally low precision -- and they can easily know whether they have,
because the "rounded result" flag would get set in the context).

> Whether Decimal itself allows an optional rounding-policy (including
> of course "no rounding" as a possibility) is one issue (I guess that
> might violate some ANSI or IEEE standard...?)

The IBM spec requires that the user be able to specify rounding mode, in the
context. It sounds like the "round half up" mode is what the EU requires
(not from the link above, but from what you said). Note that Cowlishaw's
site has a "telco benchmark" wherein different steps require different
rounding disciplines; I don't think that's uncommon either; I was able to
get the exact result for that benchmark pretty easily using my FixedPoint
class, but I enjoyed the luxury of having expert knowledge of the potential
traps in advance. I expect most FixedPoint users would struggle with it.

> but I most surely do want to be able to use such policies in whatever
> arithmetic type underlies Money -- so I hope Decimal is at least
> designed so that _subclasses_ can easily provide such customized
> rounding (e.g., feature-testing for a __round__ specialmethod, not
> defined in the baseclass Decimal if need be, but offering the needed
> hook for subclasses to add the functionality).

It has to support some half dozen specific rounding modes already (the spec
requires them). The mandated modes have been reviewed by many people now
over several years, so it's tempting to call YAGNI on adding more rounding
complexity. My FixedPoint does support user-defined rounding modes; alas,
all evidence to date suggests I'm the only person to date able to define one
correctly <wink -- but doing rounding exactly right requires exact
understanding of every stinking detail of every stinking endcase -- it's not
easy the first time someone tries it>.


Tim Peters

unread,
Nov 6, 2003, 10:51:49 AM11/6/03
to pytho...@python.org
[Aahz]

> Well, sure. And it won't be hard to add given that Decimal will
> already need to track thread-specific Context. But modules changing
> Context will still need to explicitly push and pop Context because
> usually you will just want to replace the current Context.

They have another choice, because Guido had a brilliant idea: the
arithmetic operations in Eric's implementation are methods *of* a context
object (because Guido suggested that). So a maximally robust library
doesn't *have* to change the thread context at all: it can create whatever
private context object(s) it needs, and spell arithmetic as explicit method
calls on its private context object(s), so that the default thread context
object is neither consulted nor modified. This is very robust, and in small
doses is quite bearable.


Bengt Richter

unread,
Nov 6, 2003, 11:27:14 AM11/6/03
to
On Thu, 06 Nov 2003 08:22:15 GMT, Alex Martelli <al...@aleax.it> wrote:

>Tim Peters wrote:
> ...
>> You do need to explicitly round (if desired) after * and /. Because of
>> the high value attached to 100% conformance to imposed standards in
>> accounting apps, I think it's a good thing that you're forced to round
>> explicitly when the result of a calculation can't be represented exactly
>> in the mandated format -- those are exactly the places where your app will
>> fail to meet the requirements, so it's good to be aware of them.
>
>European Union directives (adopted as laws by member states) mandate
>the rounding procedure to be used in computations involving Euros (round
>to closest Eurocent, always round up on half-Eurocent results); they very
>explicitly mention that this may give a 1-Eurocent discrepancy compared
>to "exact" arithmetic, and give examples; they establish that such a 1-cent
>discrepancy that comes from following exactly the prescribed rules is NOT
>legal cause for any lawsuit whatsoever; they earnestly recommend that all
>computing equipment and programs follow these same rules to avoid the huge
>headaches that would result in trying to reconcile accounts otherwise.
>
>Thus, for most accounting programs intended to run within the EU (not just
>in Euros: these provisions also apply to the other non-Euro currencies, as
>far as EU law is concerned), I do NOT think it would be a good thing for
>the programmer to have to remember to round explicitly -- the legal mandate
>is about rounding rules and it's quite easy to avoid the "fail to meet the
>requirements", as they seem designed to be easy to meet.

I wonder how they deal with compound interest. E.g., may a contract for a loan
express interest as mathematically continuous between payments? In that case
does rounding from mathematically exact values occur only at payment times?
Is the rounding only applied to the payment amount, so that the princal is
decreased by the exact quantized payment, but the balance retains exactness?
Or is requantization of all values mandated at each transaction? Is each step in
a sum a separate transaction? What about the rules for pre-tax-calculation subtotals
vs accumulating actual receipt stated tax amounts? I remember as a kid bugging
store clerks to sell me two candies separately to stay under the minimum taxable
price threshold twice rather than exceed it once and pay the extra penny ;-)

ISTM these things are 'way beyond the concern of a math package per se, yet a math package
that is intended to support programming of a business/rule/law-abiding application
would have to make it easy to express in program source what the unambiguous intent is,
and not have the math be playing unexpected tricks with the values.

IMO to have rounding/precision applied sub-expression by subexpression is a recipe
for unpleasant surprises. It would be like applying snap-to-grid rules in a drawing
package at every internal intermediate math subexpression evaluation step of the
implementation. BTW, does this mean expressions can't be run through optimizers that
might e.g., hoist a subexpression out of a loop and thereby change how many times
a rounding effect happens? What about advice to eager programmers who want to do the same
optimization by hand? Must they be advised? ISTM programmers will not want to face
these issues in internal implementation, and will instead turn up precision to
close-enough-to-exact, and then just round when they print or interact with databases etc.,
ajusting ad hoc to correct infractions of rules revealed in testing. Chances are that's
what they would wind up doing anyway, using step-wise-rounding-enforcing math.

>
>Whether Decimal itself allows an optional rounding-policy (including of
>course "no rounding" as a possibility) is one issue (I guess that might
>violate some ANSI or IEEE standard...?) but I most surely do want to be
>able to use such policies in whatever arithmetic type underlies Money --
>so I hope Decimal is at least designed so that _subclasses_ can easily
>provide such customized rounding (e.g., feature-testing for a __round__
>specialmethod, not defined in the baseclass Decimal if need be, but
>offering the needed hook for subclasses to add the functionality).
>

Agreed -- that something is needed to make defining and coding special rules on top
of the math base easy. I was thinking subclassing too. OTOH, casting about
for the meaning of "context" in this PEP context (;-), I think you could also
develop an implementation convention around a separate Context class, that
would provide instances as customized attribute-name-space contexts, using
__[gs]etattr__ overrides and/or properties, maybe with a metaclass to make parameterized
customization easy to write. Using this sort of thing, you could cause property/attribute
retrieval to return rule-enforcing wrapper objects with money quantities inside,
and any statements and math expressions written in terms of those would
be defined by the wrappers, and assignments would be defined by the context object
whose attribute space was being assigned to.

So long as people are willing write stuff like, e.g., e.total and e.tax
for total and tax values they want operated upon subject to, e.g., custom
Euro rules, IWT that could work.

But I think it's hard to anticipate what will work out slick, so some alternative
implementation sketches for a realistic use case would help clarify. Better
to concentrate on powerful orthogonal primitives and test them against a reference
problem than provide too much of what amounts to speculative app macros, IMO.

Regards,
Bengt Richter

Aahz

unread,
Nov 6, 2003, 1:07:24 PM11/6/03
to
In article <mailman.446.1067979...@python.org>,
Batista, Facundo <FBat...@uniFON.com.ar> wrote:

>Aahz wrote:
>
>#- >When passing floating point to the constructor, what should happen?
>#- >
>#- > j. ``Decimal(1.1) == Decimal('1.1')``
>#- > k. ``Decimal(1.1) ==
>#- >Decimal('110000000000000008881784197001252...e-51')``
>#-
>#- That's tough. I'm inclined toward requiring an explicit conversion
>#- through a function call that isn't the Decimal constructor,
>#- but ease of
>#- use is also a factor.
>
>We can have a Decimal.fromFloat(...), but with behaviour j or k?

k

The user can always apply appropriate rounding.

>#- >2. The value could be of the type:
>#- >
>#- > - another Decimal
>#- > - int or long
>#- > - float
>#- > - string
>#-
>#- Also a tuple of int/longs.
>
>I didn't include that in the prePEP because it seems that the actual
>implementation support this just to dec == Decimal(repr(dec)), for every
>Decimal as dec.
>
>Has any other advantage?

Yes: it means that someone who has numeric values representing a Decimal
does not need to convert them to a string.

Batista, Facundo

unread,
Nov 6, 2003, 1:35:37 PM11/6/03
to Python-List (E-mail)
Tim Peters wrote:

#- They have another choice, because Guido had a brilliant idea: the
#- arithmetic operations in Eric's implementation are methods
#- *of* a context
#- object (because Guido suggested that). So a maximally robust library

Now I can explain to myself those lines of code, :)

. Facundo

Batista, Facundo

unread,
Nov 6, 2003, 1:30:17 PM11/6/03
to Python-List (E-mail)

Aahz wrote:

#- >  Enter the context stack.
#-
#- Well, sure.  And it won't be hard to add given that Decimal
#- will already
#- need to track thread-specific Context.  But modules changing Context
#- will still need to explicitly push and pop Context because
#- usually you
#- will just want to replace the current Context.

OK. So, as long Decimal comply with the "a Context per thread" item, it's everything allright?

Batista, Facundo

unread,
Nov 6, 2003, 1:50:43 PM11/6/03
to Python-List (E-mail)
Aahz wrote:

#- >#- >2. The value could be of the type:


#- >#- >
#- >#- > - another Decimal

#- >#- > - int or long
#- >#- > - float
#- >#- > - string

#- >#-
#- >#- Also a tuple of int/longs.

#- >
#- >I didn't include that in the prePEP because it seems that the actual
#- >implementation support this just to dec ==
#- Decimal(repr(dec)), for every
#- >Decimal as dec.
#- >
#- >Has any other advantage?
#-
#- Yes: it means that someone who has numeric values
#- representing a Decimal
#- does not need to convert them to a string.

Ok. I'll modify the item 2.

. Facundo

Batista, Facundo

unread,
Nov 6, 2003, 1:49:03 PM11/6/03
to Python-List (E-mail)
bo...@oz.net wrote:

#- I wonder how they deal with compound interest. E.g., may a
#- contract for a loan
#- express interest as mathematically continuous between
#- payments? In that case
#- does rounding from mathematically exact values occur only at
#- payment times?
#- Is the rounding only applied to the payment amount, so that
#- the princal is
#- decreased by the exact quantized payment, but the balance
#- retains exactness?
#- Or is requantization of all values mandated at each
#- transaction? Is each step in
#- a sum a separate transaction? What about the rules for
#- pre-tax-calculation subtotals
#- vs accumulating actual receipt stated tax amounts? I
#- remember as a kid bugging
#- store clerks to sell me two candies separately to stay under
#- the minimum taxable
#- price threshold twice rather than exceed it once and pay the
#- extra penny ;-)

The problem I see when you round is that

d * (a + b) != d*a + d*b

i.e.:

d = 1.23
a = 5.15
b = 3.66

half-up rounding in two decimals on *each* operation:

1) 10.84
2) 10.83

I think that is up to the human, knowing that he's rounding, to write the
expression in one or other way. Maybe following legislation, maybe at
random. But it seems to me pretty difficult to the class to predict each
behaviour and get the same result in both cases.

. Facundo

John Roth

unread,
Nov 6, 2003, 3:07:22 PM11/6/03
to

"Tim Peters" <tim...@comcast.net> wrote in message
news:mailman.487.1068133...@python.org...

I think I agree: Guido committed a brilliancy there. Having had to deal
with monetary calculations and the weird rounding mandated by various
laws, regulations and way out of it PHB accountants, I don't see how
simple expression syntax is ever going to do what is needed.

AFAICS, there are only two solutions:

<decimal>.add(<number>, [<result spec>])

and

<context>.add(<decimal>, <number>)

The difference is in where you put the various factors.

I think they both come out to roughly the same number of
keystrokes, especially if you assume that <result spec> can
be a variety of different things, including a full blown context
object.

John Roth

>
>


Aahz

unread,
Nov 6, 2003, 3:37:52 PM11/6/03
to
In article <vqlaif4...@news.supernews.com>,

John Roth <newsg...@jhrothjr.com> wrote:
>
>AFAICS, there are only two solutions:
>
><decimal>.add(<number>, [<result spec>])
>
>and
>
><context>.add(<decimal>, <number>)

Both are too inconvenient for casual users. I think that for the Money
subclass, it'll probably make sense to require users to specify an
explicit Context before the first operation (raising an exception if not
initialized). For Decimal, using the same defaults as Rexx will
probably work just fine.

Aahz

unread,
Nov 6, 2003, 3:40:23 PM11/6/03
to
[BTW, Facundo, you proably need quote-printable most of the time to deal
with Spanish, but it adds a lot of cruft (left in below) when dealing
with plain English. If you could figure a way to disable that, it'd be
a big help.]

In article <mailman.491.1068143...@python.org>,
Batista, Facundo <FBat...@uniFON.com.ar> wrote:
>This message is in MIME format. Since your mail reader does not understand
>this format, some or all of this message may not be legible.
>
>------_=_NextPart_001_01C3A494.06BF20D0
>Content-Type: text/plain;
> charset="iso-8859-1"
>Content-Transfer-Encoding: quoted-printable


>
>Aahz wrote:
>
>#- > Enter the context stack.

>#-=20
>#- Well, sure. And it won't be hard to add given that Decimal=20


>#- will already
>#- need to track thread-specific Context. But modules changing Context

>#- will still need to explicitly push and pop Context because=20


>#- usually you
>#- will just want to replace the current Context.
>

>OK. So, as long Decimal comply with the "a Context per thread" item, =
>it's
>everything allright?

For the most part, though you'll need to add the Context stack at some
point. That can be left for later, once we see how Decimal gets used in
practice; Guido's idea may work better.

John Roth

unread,
Nov 6, 2003, 3:49:51 PM11/6/03
to

"Aahz" <aa...@pythoncraft.com> wrote in message
news:boebf0$ird$1...@panix1.panix.com...

> In article <vqlaif4...@news.supernews.com>,
> John Roth <newsg...@jhrothjr.com> wrote:
> >
> >AFAICS, there are only two solutions:
> >
> ><decimal>.add(<number>, [<result spec>])
> >
> >and
> >
> ><context>.add(<decimal>, <number>)
>
> Both are too inconvenient for casual users. I think that for the Money
> subclass, it'll probably make sense to require users to specify an
> explicit Context before the first operation (raising an exception if not
> initialized). For Decimal, using the same defaults as Rexx will
> probably work just fine.

The trouble is that most of the difficulty that's being discussed
is not for casual users. It's for professional users that have
to adhere to legal, regulatory and other requirements that have
no inherent underlying order that makes them amenable to
a nice, neat and trivially understandable solution.

If coming up with something that is understandable to novices
is a rock bottom requirement, then I most respectfully suggest
that the rest of the discussion is a waste of time.

The part of my response which you didn't quote said something
about my not being able to find an expression syntax that would
handle these essentially arbitrary and capricious regulations. If
you would suggest one, I'd be delighted.

Of course, I do have a suggestion for an infix notation that
would work. I predict that it will be absolutely unacceptable
to all parties.

<number> [+, <context>] <number>

In the spirit (if not the letter) of PEP 225. Notice that it simply
distributes the pieces of the expression differently.

John Roth

Aahz

unread,
Nov 6, 2003, 4:37:46 PM11/6/03
to
In article <vqld259...@news.supernews.com>,

John Roth <newsg...@jhrothjr.com> wrote:
>
>The trouble is that most of the difficulty that's being discussed is
>not for casual users. It's for professional users that have to adhere
>to legal, regulatory and other requirements that have no inherent
>underlying order that makes them amenable to a nice, neat and trivially
>understandable solution.
>
>If coming up with something that is understandable to novices is a rock
>bottom requirement, then I most respectfully suggest that the rest of
>the discussion is a waste of time.

Well, I disagree with that.

>The part of my response which you didn't quote said something
>about my not being able to find an expression syntax that would
>handle these essentially arbitrary and capricious regulations. If
>you would suggest one, I'd be delighted.

Oh, I don't think there's a purely expression-based syntax that will
solve that problem. But because it's easy enough (or should be -- I
still haven't looked at the most recent version of the code) to create
context objects and then use them to switch the active context between
operations, I think that expert programmers will have little difficulty
parameterizing their operations. Using named contexts should make
things reasonably easy to read.

Or, as Tim pointed out, the current code allows one to call operations
directly on a context. That's pretty close to what you were asking for
earlier.

Jp Calderone

unread,
Nov 6, 2003, 4:09:16 PM11/6/03
to pytho...@python.org
On Thu, Nov 06, 2003 at 03:37:52PM -0500, Aahz wrote:
> In article <vqlaif4...@news.supernews.com>,
> John Roth <newsg...@jhrothjr.com> wrote:
> >
> >AFAICS, there are only two solutions:
> >
> ><decimal>.add(<number>, [<result spec>])
> >
> >and
> >
> ><context>.add(<decimal>, <number>)
>
> Both are too inconvenient for casual users. I think that for the Money
> subclass, it'll probably make sense to require users to specify an
> explicit Context before the first operation (raising an exception if not
> initialized). For Decimal, using the same defaults as Rexx will
> probably work just fine.

That sounds like it would work, if you are forced to specify the context
on a per-instance basis. Otherwise, I don't see how two different modules
which wanted to treat Money differently could ever be used together.

How about this instead?

import context

class Decimal:
ROUNDING_STYLE = 'decimal-rounding-style'
ITALIAN, FRENCH, AMERICAN = range(3)

def __add__(self, other):
rstyle = context.get(Decimal.ROUNDING_STYLE, 'sane default')
# Do stuff, depending on rstyle
return stuff


def jiggerSomeMoneys(a, b, c):
# Lots of operations on a, b, and c

context.call({Decimal.ROUNDING_STYLE: Decimal.ITALIAN}, jiggerSomeMoneys, a, b, c)


This way, only the top-most invocation need deal with the context. The
actual implementation dealing with decimals is free to ignore it completely,
and should it require a change, there is only a single place where that
change is required.

As an added bonus, the context code is completely independent of the
decimal code, and can be re-used for any other application requiring
contextual data.

Implementations of context.call and context.get are left as an exercise to
the reader (Eh, ok, I can show you mine if anyone is actually interested :)

Jp


aa...@pythoncraft.com

unread,
Nov 6, 2003, 4:37:46 PM11/6/03
to pytho...@python.org

aa...@pythoncraft.com

unread,
Nov 6, 2003, 3:37:52 PM11/6/03
to pytho...@python.org

John Roth <newsg...@jhrothjr.com> wrote:
>
>AFAICS, there are only two solutions:
>
><decimal>.add(<number>, [<result spec>])
>
>and
>
><context>.add(<decimal>, <number>)

Both are too inconvenient for casual users.  I think that for the Money
subclass, it'll probably make sense to require users to specify an
explicit Context before the first operation (raising an exception if not
initialized).  For Decimal, using the same defaults as Rexx will
probably work just fine.

--
Aahz (aa...@pythoncraft.com)           <*>         http://www.pythoncraft.com/

John Roth

unread,
Nov 6, 2003, 3:49:51 PM11/6/03
to pytho...@python.org

> In article <vqlaif4...@news.supernews.com>,
> John Roth <newsg...@jhrothjr.com> wrote:
> >
> >AFAICS, there are only two solutions:
> >
> ><decimal>.add(<number>, [<result spec>])
> >
> >and
> >
> ><context>.add(<decimal>, <number>)
>
> Both are too inconvenient for casual users.  I think that for the Money
> subclass, it'll probably make sense to require users to specify an
> explicit Context before the first operation (raising an exception if not
> initialized).  For Decimal, using the same defaults as Rexx will
> probably work just fine.

The trouble is that most of the difficulty that's being discussed

is not for casual users. It's for professional users that have
to adhere to legal, regulatory and other requirements that have
no inherent underlying order that makes them amenable to
a nice, neat and trivially understandable solution.

If coming up with something that is understandable to novices
is a rock bottom requirement, then I most respectfully suggest
that the rest of the discussion is a waste of time.

The part of my response which you didn't quote said something

about my not being able to find an expression syntax that would
handle these essentially arbitrary and capricious regulations. If
you would suggest one, I'd be delighted.

Of course, I do have a suggestion for an infix notation that

would work. I predict that it will be absolutely unacceptable
to all parties.

<number> [+, <context>] <number>

In the spirit (if not the letter) of PEP 225. Notice that it simply
distributes the pieces of the expression differently.

John Roth


aa...@pythoncraft.com

unread,
Nov 6, 2003, 3:40:23 PM11/6/03
to pytho...@python.org

--
Aahz (aa...@pythoncraft.com)           <*>         http://www.pythoncraft.com/

Batista, Facundo

unread,
Nov 7, 2003, 11:34:39 AM11/7/03
to Python-List (E-mail)
Aahz wrote:

#- [BTW, Facundo, you proably need quote-printable most of the
#- time to deal
#- with Spanish, but it adds a lot of cruft (left in below) when dealing
#- with plain English. If you could figure a way to disable
#- that, it'd be
#- a big help.]

I'm stuck with Outlook behind an Exchange server. I'll see what I can do.


#- >OK. So, as long Decimal comply with the "a Context per
#- thread" item, =
#- >it's
#- >everything allright?
#-
#- For the most part, though you'll need to add the Context
#- stack at some
#- point. That can be left for later, once we see how Decimal
#- gets used in
#- practice; Guido's idea may work better.

OK.

. Facundo

Jp Calderone

unread,
Nov 6, 2003, 4:09:16 PM11/6/03
to pytho...@python.org

On Thu, Nov 06, 2003 at 03:37:52PM -0500, Aahz wrote:

> In article <vqlaif4...@news.supernews.com>,
> John Roth <newsg...@jhrothjr.com> wrote:
> >
> >AFAICS, there are only two solutions:
> >
> ><decimal>.add(<number>, [<result spec>])
> >
> >and
> >
> ><context>.add(<decimal>, <number>)
>
> Both are too inconvenient for casual users.  I think that for the Money
> subclass, it'll probably make sense to require users to specify an
> explicit Context before the first operation (raising an exception if not
> initialized).  For Decimal, using the same defaults as Rexx will
> probably work just fine.

  That sounds like it would work, if you are forced to specify the context

Aahz

unread,
Nov 7, 2003, 7:10:33 PM11/7/03
to
In article <mailman.503.1068153...@python.org>,

Jp Calderone <exa...@intarweb.us> wrote:
>On Thu, Nov 06, 2003 at 03:37:52PM -0500, Aahz wrote:
>>
>> Both are too inconvenient for casual users. I think that for the Money
>> subclass, it'll probably make sense to require users to specify an
>> explicit Context before the first operation (raising an exception if not
>> initialized). For Decimal, using the same defaults as Rexx will
>> probably work just fine.
>
> That sounds like it would work, if you are forced to specify the context
>on a per-instance basis. Otherwise, I don't see how two different modules
>which wanted to treat Money differently could ever be used together.

That doesn't make much sense to me. Can you provide an actual use case?

Ron Adam

unread,
Nov 7, 2003, 9:01:55 PM11/7/03
to
On Thu, 6 Nov 2003 10:43:52 -0500, "Tim Peters" <tim...@comcast.net>
wrote:

I agree with you on this, and this is an excellent example of the
situation.

I believe a general use built in decimal type which addresses the
problems with floating point would be great. It should have good
general numeric tools to manipulate the numbers and digits, including
rounding up or down but maybe not rounding even. A function to count
the number of digits before and after the decimal place might be good.
I just looked for that one and didn't see it in the math.py or
cmath.py modules. I was thinking of using it to round the decimal
portion of a number to one less digit to compensate for the floating
point internal base two representation.

As for accounting specific features, I really think a good separate
accounting math module would be better choice. It could have a rich
set of functions for calculating interest rates and Time-Value-Money
calculations such as mortgage payments, and loan Amortization lists
along with round_even and include most functions that one would find
on a business calculator or in a spreadsheet for that purpose. There
could also be euro/country specific functions if they are needed. A
module such as this may be able to support localization better than
trying to build it in.

I don't know (yet) how python handles things internally since I'm
still new with it, but I lean towards not letting the core get too
bloated or specialized, especially if it will slow it down in any way.
When it comes to numbers, speed is important.

By the way, I haven't seen anything yet on how fast the decimal type
will be compared to floats and ints? From reading the specs on it I
get the impression its slower than ints but has compression so uses
less memory, but I was wandering how much slower? Or maybe it isn't
slower if it is handled as an integer with a base ten integer
exponent?

_Ronald Adam
rad...@tampabay.rr.com


Mel Wilson

unread,
Nov 8, 2003, 10:49:10 AM11/8/03
to
In article <mailman.486.1068133...@python.org>,

"Tim Peters" <tim...@comcast.net> wrote:
> The conversion of prices into euros will require the use of a six-
> significant-digit conversion rate (six digits disregarding initial
>zeros)
> which should not be rounded or truncated. This rate will be irrevocably
> fixed on 1 January 1999 and defined in the form of one euro expressed in
> national currencies.
>
>So that part mandates a *floating* point input (not meaning binary floating
>point, but "6 digits disregarding initial zeros" is the essence of floating
>point -- the total number of digits isn't fixed, nor is the # of digits
>after the decimal point fixed, just the # of *significant* digits).
>
> To convert from national currencies to the euro, one has to divide by
> the conversion rate. To convert from the euro to the national currency,
> one has to multiply by the conversion rate. The use of inverse rates is
> forbidden.
>
>Neutral.
>
> To convert from one national currency to another, amounts must be first
> converted into euros and then into the second national currency. The
> euro amount must be rounded to three decimal places. The national
> currency should then be rounded to two decimals.
>
>So no single fixed-point discipline can suffice: in one direction they want
>rounding to 3 digits after the decimal point, in the other to 2 digits, and
>one of the inputs is a floating-point value with no fixed number of digits
>after the decimal point.

The toy Decimal I'm playing with has a class variable
specifying number of digits to the right of the decimal
point, and this gets used on calls to rounding method(s).
By deriving Euros and Dollars from the Decimal class with
the right digit specifications and calling for rounding at
the right time I might be able to do that.

Regards. Mel.

Mel Wilson

unread,
Nov 8, 2003, 10:11:31 AM11/8/03
to
In article <vqgapfo...@news.supernews.com>,
"John Roth" <newsg...@jhrothjr.com> wrote:
>My personal opinion in the matter is that setting the precision high
>enough so that you won't get into trouble is a hack, and it's a dangerous
>hack because the amount of precision needed isn't directly related to the
>data you're processing; it's something that came out of an analysis,
>probably by someone else under some other circumstances. Given
>a software implementation, there's a performance advantage to setting it as
>low
>as possible, which immediately puts things at risk if your data
>changes.

It puzzles me. In the COBOL days, we used to worry over
setting the sizes of our data fields large enough. We'd set
a size we thought was ridiculously large and then worry
whether today would be the day that the company would do the
unprecedented amount of business that would blow up the
night-shift batch runs. We escaped from that with Python's
long ints and now we're trying to invent it again.

>Fixed precision is also counter to the infinite precision we're moving
>toward with the integers; and integers are a more comfortable metaphor
>for money than floats.

>The natural implementation for fixed decimal is much simpler
>than for floating decimal. And I frankly don't think that floating
>decimal is going to get that much use outside of an accounting
>context, given that I think it's going to be a *lot* slower than
>built-in binary floating point. Especially if PyPy succeeds in
>their dream of creating a JIT for Python.

Float decimal can nice for intermediate results because
you don't have to worry about precision limits until you
reach a point where you have a specific need for a specific
precision. Software speed won't beat hardware speed, but a
float multiply can still be just

return self.__class__ (self._v*other._v, self._e+other._e)

multiplying two longs and adding two ints.

Add is tougher

v, vo, e = self._match_exp (self, other)
return self.__class__ (v+vo, e)
...
def _match_exp (self, other):
v, e = self._v, self._e
vo, eo = other._v, other._e
de = e - eo
if de > 0:
v *= pow (10, de)
elif de < 0:
vo *= pow (10, -de)
return v, vo, min (e, eo)


With COBOL we had powerful declarations in the data
division and we could fully control the precision of every
intermediate result in a calculation; of course the
one-verb-per-line form also encouraged this. In
ALGOL-flavoured languages the expression-oriented statements
make this less natural. And Python with no variable
declarations makes this hardest of all.


Regards. Mel.

Bengt Richter

unread,
Nov 8, 2003, 5:42:13 PM11/8/03
to
On Thu, 6 Nov 2003 10:43:52 -0500, "Tim Peters" <tim...@comcast.net> wrote:
[...]

>
>It has to support some half dozen specific rounding modes already (the spec
>requires them). The mandated modes have been reviewed by many people now
>over several years, so it's tempting to call YAGNI on adding more rounding
>complexity. My FixedPoint does support user-defined rounding modes; alas,
>all evidence to date suggests I'm the only person to date able to define one
>correctly <wink -- but doing rounding exactly right requires exact
>understanding of every stinking detail of every stinking endcase -- it's not
>easy the first time someone tries it>.
>

Following is an exact decimal/<somewhat>rational module hack that implements rounding
when the .round() method is called. This is not too elegant, but it might make
an interesting toy to cross-check results with. 30 years of monthly compound interest
does generate a fair amount of digits, but it can handle it. And you can check the _exact_
difference from doing it floating point, and perhaps demonstrate when/how it's safe
to use hardware floating point (the below has a software decimal floating point).

About the 30 years interest,

>>> from exactdec import ED
>>> monthly5 = 1 + ED( '.05/12')
>>> monthly5
ED('3.0125 / 3')
>>> start = ED(1)
>>> start
ED('1')
>>> fm5 = 1.0+ 0.05/12
>>> fm5
1.0041666666666667
>>> monthly5.round(20)
ED('1.00416666666666666667')
>>> fstart = 1.0
>>> for i in xrange(30*12):
... fstart *= fm5
... start *= monthly5
...
>>> fstart
4.4677443140061053
>>> start.round(20)
ED('4.46774431400613221243')
>>> err = ED(fstart,'all')-start
>>> err
ED('-1.56304148231013236986795175352733832996669654337153733730960097118272041942305024046142360
963012057392399385808513132735004380045887019491590594848567543746099279247782492404211092305697
981942603081504204074464788922982296549717752974330439000221572233435214122399335151325954144618
900462032107403557206447160788112551619335537177864834308165003815651982743545600184830416792430
006198225629411824849523906416912497782230577924310271500267297956136382334806613723525165395284
535900838608901737436464025004714288622793588896506577479134017475377641227803205011713549091096
133145948633022116071771866555211557151726225215476514565787630190565855468620450845083504202642
689190693005234276689807253839309304532499468247786496752880094937190474830588626230621777889479
703217124659874396057456877927948952659639736522210676920364370104538827117231761130501253771264
878907949516858455102771211294274323473541556175745838790698849963815157512517119109683330894122
419465602517028421797269789171429961866520967620825108423637749408425943209381542284540329915368
857997235383065705584065571271008721284350021123549868449933295248144722514707438777251932770746
090987731235646452226688847245082616867972045549031014767048143096992525819923039646122088361644
579918406562388873940433411106385019399448321943189013300306911684700844253559215810364410055218
335449640228259572789992966032626222008918446886031635163380806543318900671084821575936160455692
493336358710092579196071811371493642452908504603585136777462572632798060122885204925037488881947
910781074947690321238995118414971674525304479175247251987457275390625e158 / 58029883553010642635
729412690188122648867574983421180398276039568933683892061549947054264296629049795373656629387735
93537918329060451697709838267789886857631025156060199201')
>>> err.round(50)
ED('-2.693511319701830367844690393524096646e-14')
>>> err.round(49)
ED('-2.69351131970183036784469039352409665e-14')
>>> err.round(48)
ED('-2.6935113197018303678446903935240966e-14')

But there is a lot of digits in the exact result, and the percentage error of the floating
point isn't so bad:

>>> (err/start).round(30)
ED('-6.028794690103059e-15')


====< exactdec.py >====================================================================
# exactdec.py -- exact decimal/rational class
# 20031107 14:54:21 alpha -- bokr
# 20031108 09:42:18 added contruction from float with all info or specified rounding -- bokr
# 20031108 13:58:10 added more docs and interactive calculator thing -- bokr
#
# WARNING: Not signigicantly tested. Not optimized. No warranty. Use at your own risk. And,
# if you run this program interactively, it passes your raw_input to eval unvalidated!
#
# The ED class preserves accuracy exactly by including a denominator for a kind of
# hybrid rational. The info defining the value can be retrieved via ED.astuple
# as a 3-tuple of (numerator, denominator, power_of_10) for the exact value
# (numerator*10**power_of_10)/denominator.
#
# Currently, rounding is a separate operation that returns an exact result. Only rounding
# away from zero on halves is implemented (and not very well tested ;-)
#
# Examples of valid string literals being passed to the constructor are:
# ED('-12.34e-56/78') -- which by the way currently reprs as the exactly equivalent
# normalized ED('-6.17e-56 / 39')
# ED('.1') which reprs as ED('.1')
# Converting the same floating point value preserving total or otherwise specified
# accuracy get you:
# ED(.1, 'all') => ED('1.000000000000000055511151231257827021181583404541015625e-1')
# ED(.1, 24) => ED('1.00000000000000005551115e-1')
# ED(.1, 23) => ED('1.0000000000000000555112e-1')
# ED(.1, 22) => ED('1.000000000000000055511e-1')
# ED(.1, 20) => ED('0.10000000000000000555')
# ED(.1, 19) => ED('0.1000000000000000056')
# ED(.1, 18) => ED('0.100000000000000006')
# ED(.1, 17) => ED('0.10000000000000001')
# ED(.1, 16) => ED('0.1')
#
# Integers and longs are accepted as accurate, and value will be preserved, though
# representation may be normalized.
#
# ED(2L**55) => ED('36028797018963968')
# ED(20L**55) => ED('3.6028797018963968e71')
# ED(10L**55) => ED('1.0e55')
# The inverse can be calculated accurately, and mutiplied out again:
# 1 / ED(10L**55) => ED('1.0e-55')
# 1 / ED(20L**55) => ED('2.77555756156289135105907917022705078125e-72')
# (1 / ED(20L**55)) * ED(2**55) => ED('1.0e-55')
# (1 / ED(20L**55)) * ED(2**55) * ED('1e55') => ED('1')
#
#############################################################################
#
# The literal for an exact value is a string
import re
rxo = re.compile(r'\s*([+-]?\d*)(\.(\d*))?(e([+-]?\d+))?(\s*/\s*([+-]?\d+))?')
# example
# rxo.search('12.34e-45 / 678' ).groups()[::2]
# ('12', '34', '-45', '678')

def fracnorm(num,den):
"""make numerator carry sign and return equivalent num,den with no common divisor"""
if num==0: return 0,1
elif den==0: raise ValueError,'zero fracnorm den!'
if den<0: num=-num; den=-den
n=abs(num); d=den
if n<d: n,d = d,n
while d != 0: n, d = d, n % d
return num//n, den//n

class ED(object):
"""exact decimal"""
def __init__(self, v=0, decimals=None):
"""
decimals is only mandatory for exactifying float, done by rounding
to a least significant digit of 10**-decimals (decimals may be <=0),
but may be specified as 'all' to capture all bits of float as an accurate value.
"""
if isinstance(v, (int,long)):
self.num = v
self.den = 1
self.p10=0
elif isinstance(v, str):
"""r'\s*([+-]?\d*)(\.(\d*))?(e([+-]?\d+))?(\s*/\s*([+-]?\d+))?'"""
# XXX needs validation checks
# <i1>.<i2>e<p10> / <den>
i1,i2,p10,den = rxo.search(v).groups()[::2]
if den is None: den = 1
else: den = int(den)
if p10 is None: p10=0
else: p10 = int(p10)
if i2 is None: i2=''
p10 -= len(i2)
self.num = int(i1+i2)
self.den = den
self.p10 = p10
elif isinstance(v, float):
if decimals is None or (not isinstance(decimals, int) and decimals!='all'):
raise ValueError(
"Specify decimals for least significant digit of 10**(-decimals)\n"
"(decimals may also be specified as 'all' to capture all bits of float)")
sign = v<0
if sign: v = -v
acc = type(self)(0)
p2=1; shift2=2**27 # usual double has 53 bits
while v:
i = int(v) # depend on accurate truncation and promotion to long
acc = acc + type(self)((i,p2,0))
v = (v-i)*shift2
p2 *= shift2
if sign: acc.num = -acc.num
self.num = acc.num
self.den = acc.den
self.p10 = acc.p10
elif isinstance(v, type(self)):
self.num = v.num
self.den = v.den
self.p10 = v.p10
elif isinstance(v, tuple):
self.num = v[0]
self.den = v[1]
self.p10 = v[2]
else:
raise ValueError, 'Exactdec constructor does not accept %r' %type(v)
self.normalize()
if decimals !='all' and decimals!=None:
self.num, self.den, self.p10 = self.round(decimals).astuple()

def __repr__(self):
self.normalize()
num = self.num; den=self.den; p10=self.p10
s = str(abs(num))
sign = '-'[:num<0] # '' or '-'
dens = den != 1 and ' / %s'%den or ''
# normalize to x.xxxxe<p10> if total string > 20 long
zs = len(s)+p10 # leading digits or leading-zero deficit if negative
if (zs<0 and -zs or p10)+len(s)>20:
return "ED('%s%s.%se%s%s')" %(sign, s[0], s[1:].rstrip('0') or '0', zs-1, dens)
zs = zs<0 and '0'*-zs or ''
s = zs+s
if p10>=0:
return "ED('%s%s%s%s')" %(sign, s, '0'*p10, dens)
elif zs>0:
return "ED('%s%s.%s%s')" %(sign, s[:p10] or '0', s[p10:], dens)
else:
return "ED('%s%se%s%s')"%(sign,num,p10,dens)

def __mul__(self,other):
"""multiply exactly"""
other = type(self)(other)
num = self.num * other.num
den = self.den * other.den
p10 = self.p10 + other.p10
return type(self)((num,den,p10)).normalize()
__rmul__ = __mul__

def __add__(self, other):
"""add exactly"""
other = type(self)(other)
num = self.num; p10 = self.p10; den = self.den
numo = other.num; p10o = other.p10; deno = other.den
dshift = p10-p10o
if dshift:
if dshift<0:
p10o += dshift
numo *= 10**-dshift
else:
p10 -= dshift
num *= 10**dshift
numsum = num*deno+numo*den
densum = den*deno
return type(self)((numsum, densum, p10)).normalize()
__radd__ = __add__

def __sub__(self, other):
"""substract exactly (negates other and adds)"""
other = type(self)(other)
other.num = -other.num
return self + other

def __rsub__(self, other):
"""add as right operand"""
return type(self)(other) - self

def __div__(self,other):
"""divide exactly -- multiplies inverse"""
other = type(self)(other)
num = self.num * other.den
den = self.den * other.num
p10 = self.p10 - other.p10
return type(self)((num,den,p10)).normalize()

def __rdiv__(self, other):
"""divide in role of right arg"""
return type(self)(other)/self

def normalize(self):
"""
make num part carry sign
remove common factors of num, den parts
convert factors of 10 to exponent count
convert den factors of 2 to 10/5 and remove 10's to exponent
"""
self.num, self.den = fracnorm(self.num, self.den)
while self.num and self.num%10==0:
self.num //= 10
self.p10+=1
while not self.den%2:
self.num *=5
self.den //=2
self.p10 -=1
while not self.den%5:
self.num *=2
self.den //=5
self.p10 -=1
return self

def round(self, ndec=2):
"""
Return rounded value as exact decimal.
Round away from zero on fractions of .5 and more
XXX needs alternative rounding choices.
"""
ret = type(self)(self)
sign = ret.num<0
ret.num = abs(ret.num)
ret = ret + type(self)((5,1,-ndec-1))
psh = -(ret.p10+ndec)
ret.num //= (ret.den*10**psh)
ret.den = 1
ret.p10 += psh
if sign: ret.num = -ret.num
ret.normalize()
return ret

def astuple(self): self.normalize(); return self.num, self.den, self.p10
def __eq__(self, other): return self.astuple() == type(self)(other).astuple()
def __lt__(self, other): return (self-other).num<0
def __nonzero__(self): return self.num!=0
def __int__(self): return (self.num*10**self.p10)//self.den

def test():
import sys
for x in xrange(-9,10):
sys.stdout.write('+')
for y in xrange(-9,10):
if not y: continue # skip zero denom
for p10 in xrange(-9,10):
s='%se%s/%s'%(x,p10,y)
assert ED(s) == ED((x,y,p10))
print

if __name__ == '__main__':
print """
Enter 'test' to run test (such as it is), otherwise
Enter arguments to be passed to the ED constructor, or
prefix with *, /, +, or - to operate with previous result
Enter r <number of decimals> to round previous result.

Note that your input is passed to eval unvalidated, so
do NOT let this run where that would be dangerous!!

Enter quotes to make string literals for ED constructor.
To pass a float per se, specify decimals, e.g.,
> 0.1, 'all'
ED('0.1000000000000000055511151231257827021181583404541015625')
> 0.1, 22
ED('0.1000000000000000055511')
> 0.1, 18
ED('0.100000000000000006')
> 0.1, 16
ED('0.1')
To constrast, a string literal (second below) is accurate:
> 0.1, 'all'
ED('0.1000000000000000055511151231257827021181583404541015625')
> - '0.1'
ED('5.5511151231257827021181583404541015625e-18')

Press Enter only, to Exit."""
value = ED(0)
while 1:
try:
cmd = raw_input('%r\n> '%value).strip()
if not cmd: break
if cmd == 'test': test(); continue
if not cmd[0] in '*/+-r':
value = eval('ED(%s)'%cmd)
else:
if cmd[0]=='r':
value = eval('%s.round(%s)'%(value, cmd[1:].strip()))
else:
value = eval('%s %s ED(%s)'%(value, cmd[0], cmd[1:].strip()))
except (EOFError, KeyboardInterrupt), e:
raise SystemExit, 'Exiting due to %s'%e.__class__.__name__
except Exception, e:
print '%s: %s'%(e.__class__.__name__, e)
=======================================================================================

Regards,
Bengt Richter

Aahz

unread,
Nov 9, 2003, 3:02:21 AM11/9/03
to
In article <jeQr/ks/Kz6B...@the-wire.com>,

Mel Wilson <mwi...@the-wire.com> wrote:
>In article <vqgapfo...@news.supernews.com>,
>"John Roth" <newsg...@jhrothjr.com> wrote:
>>
>>My personal opinion in the matter is that setting the precision
>>high enough so that you won't get into trouble is a hack, and it's
>>a dangerous hack because the amount of precision needed isn't
>>directly related to the data you're processing; it's something that
>>came out of an analysis, probably by someone else under some other
>>circumstances. Given a software implementation, there's a performance
>>advantage to setting it as low as possible, which immediately puts
>>things at risk if your data changes.
>
> It puzzles me. In the COBOL days, we used to worry over setting the
>sizes of our data fields large enough. We'd set a size we thought was
>ridiculously large and then worry whether today would be the day that
>the company would do the unprecedented amount of business that would
>blow up the night-shift batch runs. We escaped from that with Python's
>long ints and now we're trying to invent it again.

Division is the fly in the ointment, unfortunately. The other operations
lead to unbounded-but-finite number sets; division leads to infinite size
unless truncated or rounded.

Bengt Richter

unread,
Nov 9, 2003, 11:52:29 AM11/9/03
to
On 9 Nov 2003 03:02:21 -0500, aa...@pythoncraft.com (Aahz) wrote:

>In article <jeQr/ks/Kz6B...@the-wire.com>,
>Mel Wilson <mwi...@the-wire.com> wrote:
>>In article <vqgapfo...@news.supernews.com>,
>>"John Roth" <newsg...@jhrothjr.com> wrote:
>>>
>>>My personal opinion in the matter is that setting the precision
>>>high enough so that you won't get into trouble is a hack, and it's
>>>a dangerous hack because the amount of precision needed isn't
>>>directly related to the data you're processing; it's something that
>>>came out of an analysis, probably by someone else under some other
>>>circumstances. Given a software implementation, there's a performance
>>>advantage to setting it as low as possible, which immediately puts
>>>things at risk if your data changes.
>>
>> It puzzles me. In the COBOL days, we used to worry over setting the
>>sizes of our data fields large enough. We'd set a size we thought was
>>ridiculously large and then worry whether today would be the day that
>>the company would do the unprecedented amount of business that would
>>blow up the night-shift batch runs. We escaped from that with Python's
>>long ints and now we're trying to invent it again.
>
>Division is the fly in the ointment, unfortunately. The other operations
>lead to unbounded-but-finite number sets; division leads to infinite size
>unless truncated or rounded.

But (a/b) / (c/d) == (a/b) * (d/c) == (a*d/b*c) so if you can deal with multiply
you can postpone truncation or rounding if you postpone actual division by maintaining
the separate rational components. Of course (a/b) + (c/d) => (a*d+b*c / b*d)
so you can wind up with a lot of multiplies too, if b!=d. I wondered what was really entailed,
so I wrote an experimental module (exactdec.py, see recent post in this thread (it is very
unoptimized, but it seems to work for interactive purposes)).

At least one can mentally shift to thinking about when and why rounding or truncation
is _really_ necessary in real use cases. Maybe eager rounding is premature optimization
and/or a no-op in a lot of cases (or worse, hidden value transformations that are not
documented as intentional).

I wonder what the legislators would mandate if they thought exact arithmetic was as cheap
and easy as hardware fixed point. Maybe currency exchange rates would be expressed as e.g.
<large_integer_number_of_dollars>/<large_integer_number_of_euros> when they were fixed legally,
and there would be no argument about using the inverse ;-) The problem would move elsewhere.
But where? Well, I don't know, presumably how to round when doing debit/credit across an exact
echange rate to accounts expressed in discrete mimimum quanta.

Hm, ... that's actually not uninteresting ;-) What does currency exchange _really_ mean?
Any given exchange that is not exact could be said to have an exactly defined error in one
or both sides of the transaction. If a price is stated in one currency, that could be presumed
exact. If the other side always truncated down to an exact amount, the exact errors could
theoretically be accumulated in an error bank, doing exact arithmetic summing fractions all
less than 1, determined by the various exchange rates in effect over time.

Ignoring the computing problems, what would be the meaning of the total in that national
error bank at the end of a year? A tax on inexact arithmetic? What is the meaning of ignoring
the amount? A leak in the monetary system? Probably only of theoretical interest compared to
other leaks ;-)

Regards,
Bengt Richter

Samuel Kilchenmann

unread,
Nov 9, 2003, 7:56:54 PM11/9/03
to
"Bengt Richter" <bo...@oz.net> schrieb im Newsbeitrag
news:bojrg5$vim$0...@216.39.172.122...

>
> Following is an exact decimal/<somewhat>rational module hack that
> implements rounding when the .round() method is called.
> This is not too elegant, but it might make
> an interesting toy to cross-check results with.

Thanks a lot for this interesting toy!

some results seem a little bit strange to me, eg.:

Python 2.3.2 (#49, Oct 2 2003, 20:02:00) [MSC v.1200 32 bit (Intel)] on
win32
Type "help", "copyright", "credits" or "license" for more information.
>>> from exactdec import ED
>>> ED('0.95',1)
ED('9..0')
>>> ED('0.995',2)
ED('99..0')
>>> ED('0.9995',3)
ED('99.9.0')
>>> ED('0.99995',4)
ED('99.99.0')
>>> ED('0.999995',5)
ED('99.999.0')
>>> ED('0.9999995',6)
ED('1.0')
>>>

its probably due to the line:
ret.num //= (ret.den*10**psh)
in your rounding code and the fact that:
>>> 1 // 0.1
9.0
>>> 10 // 0.01
999.0
>>> 100 // 0.001
99999.0
>>> 1000 // 0.0001
9999999.0
>>> 10000 // 0.00001
999999999.0
>>> 100000 // 0.000001
100000000000.0

Bengt Richter

unread,
Nov 9, 2003, 10:32:30 PM11/9/03
to
On Mon, 10 Nov 2003 01:56:54 +0100, "Samuel Kilchenmann" <skil...@bluewin.ch> wrote:

>"Bengt Richter" <bo...@oz.net> schrieb im Newsbeitrag
>news:bojrg5$vim$0...@216.39.172.122...
>>
>> Following is an exact decimal/<somewhat>rational module hack that
>> implements rounding when the .round() method is called.
>> This is not too elegant, but it might make
>> an interesting toy to cross-check results with.
>
>Thanks a lot for this interesting toy!

You're welcome ;-)

>
>some results seem a little bit strange to me, eg.:
>
>Python 2.3.2 (#49, Oct 2 2003, 20:02:00) [MSC v.1200 32 bit (Intel)] on
>win32
>Type "help", "copyright", "credits" or "license" for more information.
>>>> from exactdec import ED
>>>> ED('0.95',1)
>ED('9..0')
>>>> ED('0.995',2)
>ED('99..0')
>>>> ED('0.9995',3)
>ED('99.9.0')
>>>> ED('0.99995',4)
>ED('99.99.0')
>>>> ED('0.999995',5)
>ED('99.999.0')
>>>> ED('0.9999995',6)
>ED('1.0')
>>>>

Gak. Yes, I had seen that weirdness before, and thought I fixed it, but not all of it ;-/

>
>its probably due to the line:
> ret.num //= (ret.den*10**psh)
>in your rounding code and the fact that:
>>>> 1 // 0.1
>9.0
>>>> 10 // 0.01
>999.0
>>>> 100 // 0.001
>99999.0
>>>> 1000 // 0.0001
>9999999.0
>>>> 10000 // 0.00001
>999999999.0
>>>> 100000 // 0.000001
>100000000000.0
>

Thanks, you are are right. That line (and the companion exponent adjustment)
should not be executed unless psh>0. If psh<=0 then there are no less significant
digits to get rid of with the //. I hope this is a fix, no time for extensive test
(I have to disappear for a few days):

--- exactdec.r1.py Sat Nov 08 14:12:29 2003
+++ exactdec.r1a.py Sun Nov 09 19:18:21 2003
@@ -2,6 +2,7 @@


# 20031107 14:54:21 alpha -- bokr
# 20031108 09:42:18 added contruction from float with all info or specified rounding -- bokr
# 20031108 13:58:10 added more docs and interactive calculator thing -- bokr

+# 20031109 19:14:10 fix to round method -- thanks to Samuel Kilchenmann for bug report -- bokr


#
# WARNING: Not signigicantly tested. Not optimized. No warranty. Use at your own risk. And,
# if you run this program interactively, it passes your raw_input to eval unvalidated!

@@ -219,9 +220,10 @@


ret.num = abs(ret.num)
ret = ret + type(self)((5,1,-ndec-1))
psh = -(ret.p10+ndec)

- ret.num //= (ret.den*10**psh)
+ if psh>0:
+ ret.num //= (ret.den*10**psh)
+ ret.p10 += psh
ret.den = 1
- ret.p10 += psh


if sign: ret.num = -ret.num
ret.normalize()
return ret

Regards,
Bengt Richter

Batista, Facundo

unread,
Nov 11, 2003, 10:09:17 AM11/11/03
to Python-List (E-mail)
Ron Adam wrote:

#- As for accounting specific features, I really think a good separate
#- accounting math module would be better choice. It could have a rich
#- set of functions for calculating interest rates and Time-Value-Money
#- calculations such as mortgage payments, and loan Amortization lists
#- along with round_even and include most functions that one would find
#- on a business calculator or in a spreadsheet for that purpose. There
#- could also be euro/country specific functions if they are needed. A
#- module such as this may be able to support localization better than
#- trying to build it in.

Maybe. My idea (or roadmap, wow!) is to get Decimal working. *Then*, I'll
make a Money class. The idea is to get both into the standard library, but
that's all I need.

Maybe someone will want to make a Accounting module, based on Money, or
based on Decimal, but not me: I don't know accounting at all!


#- By the way, I haven't seen anything yet on how fast the decimal type
#- will be compared to floats and ints? From reading the specs on it I
#- get the impression its slower than ints but has compression so uses
#- less memory, but I was wandering how much slower? Or maybe it isn't
#- slower if it is handled as an integer with a base ten integer
#- exponent?

Didn't make any test. First, I'll get it working. Maybe then I'll get it
fast. Anyway, read the Aahz signature...

. Facundo

Batista, Facundo

unread,
Nov 11, 2003, 12:39:47 PM11/11/03
to Python-List (E-mail)

mwi...@the-wire.com wrote:

#-    The toy Decimal I'm playing with has a class variable
#- specifying number of digits to the right of the decimal
#- point, and this gets used on calls to rounding method(s).
#- By deriving Euros and Dollars from the Decimal class with
#- the right digit specifications and calling for rounding at
#- the right time I might be able to do that.

In Money you'll can do something like that. But not automagically in Decimal.

0 new messages