prePEP: Decimal data type

Bengt Richter bokr at oz.net
Sat Nov 1 20:41:35 EST 2003


On Fri, 31 Oct 2003 15:36:38 -0300, "Batista, Facundo" <FBatista at 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 <fbatista at 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 ;-)

>
>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('<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




More information about the Python-list mailing list