prePEP: Decimal data type

B

Batista, Facundo

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 <[email protected]>
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.
 
E

Emile van Sebille

Batista said:
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
(e-mail address removed)
 
J

John Roth

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 said:
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 <[email protected]>
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.

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?)

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.

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

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.
 
A

Alex Martelli

John said:
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.)

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
 
P

Paul Moore

John Roth said:
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:
0L


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

No way. Consider:
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.
 
J

John Roth

Alex Martelli said:
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
 
J

John Roth

Paul Moore said:
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).


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

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.
No way. Consider:

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
 
I

Irmen de Jong

John said:
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
 
A

Alex Martelli

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
 
J

John Roth

Alex Martelli said:
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.
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
 
J

John Roth

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


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!

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

John Roth
 
A

Alex Martelli

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
 
J

John Roth

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:



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.
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
 
A

Alex Martelli

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
 
J

John Roth

Alex Martelli said:
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, 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
 
A

Alex Martelli

John said:
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.

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).

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...:).

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.

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
 
A

Alex Martelli

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
 
J

John Roth

[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.

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
 
B

Bengt Richter

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 <[email protected]>
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?
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?
IMO this case should always require precision info for the base class contructor
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 ;-)
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
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(' said:
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
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,744
Messages
2,569,484
Members
44,906
Latest member
SkinfixSkintag

Latest Threads

Top