Decimal arithmetic, with example code

Bengt Richter bokr at oz.net
Tue Oct 1 17:03:53 EDT 2002


On Mon, 30 Sep 2002 21:12:00 -0500, "Chris Gonnerman" <chris.gonnerman at newcenturycomputers.net> wrote:
[... test program whose modified version appears below ...]

Hi Chris,
This is unproved, but maybe with the help of timbot et al we can prove
the range over which is is safe and useful. The test run doesn't prove
anything, but I thought it interesting. Just run it as you would your version.

----< fptester.py >---------------
# fptester.py -- adding experimental evenround test to fptest.py - bokr
#
# On Mon, 30 Sep 2002 21:12:00 -0500,
#      "Chris Gonnerman" <chris.gonnerman at newcenturycomputers.net> wrote:
#
# >This is a multi-part message in MIME format.
# >
# >------=_NextPart_000_000B_01C268C6.04954560
# >Content-Type: text/plain;
# >	charset="Windows-1252"
# >Content-Transfer-Encoding: 7bit
# >
# Ok, here it is.  I have written some Python code which
# displays the problems with float (floating binary)
# arithmetic applied to monetary amounts.  Run this, and
# many the places where the simple percentage times
# amount calculation in floating binary falls down will
# become obvious.
# 
# You need the fixedpoint.py module installed to run this.
# 
#     http://fixedpoint.sourceforge.net
# 
# (code attached)
# 
# Chris Gonnerman -- chris.gonnerman at newcenturycomputers.net
# http://newcenturycomputers.net
# ___________________________________________________________________________
#
# Chris,
# The evenround function I defined below seems (!) to do ok for your test,
# and then some (I extended it to run through with smaller and smaller
# percentages using increasing numbers of decimal fraction digits)
# 
# Warning! This is as yet unproved. I just had the idea in response to the
# discussion. So don't go putting it in production software! You have been advised ;-)
# 
# I modified your code a fair amount to incorporate and test my rounding function,
# as you can see. It would be interesting to prove what the range of validity
# really is.
# 
# Anyway, run this just as you would your version.
# BTW, straight text is nicer to post ;-)
#
# Regards,
# Bengt Richter
# ___________________________________________________________________________


"""fptester.py -- fixedpoint versus float versus evenrounded float comparison"""

# You need the fixedpoint.py module installed
#    http://fixedpoint.sourceforge.net

from fixedpoint import FixedPoint

FMTCOLS = '%-14s '*5 + '%s'
TITLES = "PCT AMT Fixed Float EFloat EFloat==Fixed".split()
print FMTCOLS % tuple(TITLES)
print FMTCOLS % tuple(map(lambda x:'-'*len(x), TITLES))

# I'm checking percentages, from 1% to 10% inclusive,
# multiplied by "money" amounts from 0.01 (one cent)
# to 1.00 (a buck).

# (I added an outer loop to scale the percentages down by factors of 10**x -- Bengt)

# Given appropriate precision, this problem applies
# to other monetary systems than just USA.

####################
# Here is my evenround function, apparently letting you use floating point as-is
# over a large range until it's time to round and print with %f format.
# -- Bengt Richter (NO WARRANTIES! Experimental software!)
#
EPSILON = 1.0e-14
BIAS = 1.0 + EPSILON
def evenround(f, nfdec):
    """
    Return floating value that will round to even trailing digit
    with nfdec fractional digits in %.0<nfdec>f format.
    """
    f, fsigned = abs(f), f
    half = 0.5 * 10.**-nfdec
    f *= BIAS
    nh, rh = divmod(f, half)
    f -= (rh<EPSILON and int(nh)%4==1)*half*BIAS
    if fsigned<0: return -f
    return f
#
####################
# Here I run the percentages through smaller-by-factors-of-ten ranges
for decimals in range(2,13):
    ffmt = '%%.0%df' % decimals
    for percent in range(1,11):

        fixed_pct = FixedPoint(percent,decimals)
        fixed_pct /= 10**decimals

        float_pct = percent / 10.0**decimals

        for amount in range(1,101):

            fixed_amt = FixedPoint(amount, decimals)
            fixed_amt /= 100

            float_amt = amount / 100.0

            ##############################################################
            # Now for the crux of the matter:

            fixed_res = fixed_amt * fixed_pct  # rounding is implicit here
            float_res = round(float_amt * float_pct, decimals)
            even_res =  evenround(float_amt * float_pct, decimals)
            ##############################################################
            # That's it, let's see if they match:

            sfix, sfloat, seven = (
                str(fixed_res), (ffmt % float_res), (ffmt % even_res)
            )
            if sfix != sfloat or sfix != seven:
                print FMTCOLS % (
                    fixed_pct, fixed_amt,   # for ease ;-)
                    sfix, sfloat, seven, ('No, BUMMER!', 'Yes')[sfix==seven]
                )

# end of file.
----------------------------------

Results are :

[14:01] C:\pywk\fixedpt>python fptester.py
PCT            AMT            Fixed          Float          EFloat         EFloat==Fixed
---            ---            -----          -----          ------         -------------
0.01           0.50           0.00           0.01           0.00           Yes
0.02           0.25           0.00           0.01           0.00           Yes
0.05           0.10           0.00           0.01           0.00           Yes
0.05           0.50           0.02           0.03           0.02           Yes
0.05           0.70           0.04           0.03           0.04           Yes
0.05           0.90           0.04           0.05           0.04           Yes
0.06           0.75           0.04           0.05           0.04           Yes
0.09           0.50           0.04           0.05           0.04           Yes
0.10           0.05           0.00           0.01           0.00           Yes
0.10           0.25           0.02           0.03           0.02           Yes
0.10           0.35           0.04           0.03           0.04           Yes
0.10           0.45           0.04           0.05           0.04           Yes
0.10           0.65           0.06           0.07           0.06           Yes
0.10           0.85           0.08           0.09           0.08           Yes
0.001          0.500          0.000          0.001          0.000          Yes
0.002          0.250          0.000          0.001          0.000          Yes
0.005          0.100          0.000          0.001          0.000          Yes
0.005          0.500          0.002          0.003          0.002          Yes
0.005          0.700          0.004          0.003          0.004          Yes
0.005          0.900          0.004          0.005          0.004          Yes
0.006          0.750          0.004          0.005          0.004          Yes
0.009          0.500          0.004          0.005          0.004          Yes
0.010          0.050          0.000          0.001          0.000          Yes
0.010          0.250          0.002          0.003          0.002          Yes
0.010          0.350          0.004          0.003          0.004          Yes
0.010          0.450          0.004          0.005          0.004          Yes
0.010          0.650          0.006          0.007          0.006          Yes
0.010          0.850          0.008          0.009          0.008          Yes
0.0001         0.5000         0.0000         0.0001         0.0000         Yes
0.0002         0.2500         0.0000         0.0001         0.0000         Yes
0.0003         0.5000         0.0002         0.0001         0.0002         Yes
0.0005         0.1000         0.0000         0.0001         0.0000         Yes
0.0005         0.3000         0.0002         0.0001         0.0002         Yes
0.0005         0.5000         0.0002         0.0003         0.0002         Yes
0.0005         0.9000         0.0004         0.0005         0.0004         Yes
0.0006         0.2500         0.0002         0.0001         0.0002         Yes
0.0006         0.7500         0.0004         0.0005         0.0004         Yes
0.0009         0.5000         0.0004         0.0005         0.0004         Yes
0.0010         0.0500         0.0000         0.0001         0.0000         Yes
0.0010         0.1500         0.0002         0.0001         0.0002         Yes
0.0010         0.2500         0.0002         0.0003         0.0002         Yes
0.0010         0.4500         0.0004         0.0005         0.0004         Yes
0.0010         0.6500         0.0006         0.0007         0.0006         Yes
0.0010         0.8500         0.0008         0.0009         0.0008         Yes
0.00001        0.50000        0.00000        0.00001        0.00000        Yes
0.00002        0.25000        0.00000        0.00001        0.00000        Yes
0.00005        0.10000        0.00000        0.00001        0.00000        Yes
0.00005        0.50000        0.00002        0.00003        0.00002        Yes
0.00005        0.70000        0.00004        0.00003        0.00004        Yes
0.00005        0.90000        0.00004        0.00005        0.00004        Yes
0.00006        0.75000        0.00004        0.00005        0.00004        Yes
0.00007        0.50000        0.00004        0.00003        0.00004        Yes
0.00009        0.50000        0.00004        0.00005        0.00004        Yes
0.00010        0.05000        0.00000        0.00001        0.00000        Yes
0.00010        0.25000        0.00002        0.00003        0.00002        Yes
0.00010        0.35000        0.00004        0.00003        0.00004        Yes
0.00010        0.45000        0.00004        0.00005        0.00004        Yes
0.00010        0.65000        0.00006        0.00007        0.00006        Yes
0.00010        0.85000        0.00008        0.00009        0.00008        Yes
0.000001       0.500000       0.000000       0.000001       0.000000       Yes
0.000002       0.250000       0.000000       0.000001       0.000000       Yes
0.000005       0.100000       0.000000       0.000001       0.000000       Yes
0.000005       0.500000       0.000002       0.000003       0.000002       Yes
0.000005       0.900000       0.000004       0.000005       0.000004       Yes
0.000006       0.750000       0.000004       0.000005       0.000004       Yes
0.000009       0.500000       0.000004       0.000005       0.000004       Yes
0.000010       0.050000       0.000000       0.000001       0.000000       Yes
0.000010       0.250000       0.000002       0.000003       0.000002       Yes
0.000010       0.450000       0.000004       0.000005       0.000004       Yes
0.000010       0.650000       0.000006       0.000007       0.000006       Yes
0.000010       0.850000       0.000008       0.000009       0.000008       Yes
0.0000001      0.5000000      0.0000000      0.0000001      0.0000000      Yes
0.0000002      0.2500000      0.0000000      0.0000001      0.0000000      Yes
0.0000005      0.1000000      0.0000000      0.0000001      0.0000000      Yes
0.0000005      0.5000000      0.0000002      0.0000003      0.0000002      Yes
0.0000005      0.9000000      0.0000004      0.0000005      0.0000004      Yes
0.0000006      0.7500000      0.0000004      0.0000005      0.0000004      Yes
0.0000009      0.5000000      0.0000004      0.0000005      0.0000004      Yes
0.0000010      0.0500000      0.0000000      0.0000001      0.0000000      Yes
0.0000010      0.2500000      0.0000002      0.0000003      0.0000002      Yes
0.0000010      0.4500000      0.0000004      0.0000005      0.0000004      Yes
0.0000010      0.6500000      0.0000006      0.0000007      0.0000006      Yes
0.0000010      0.9500000      0.0000010      0.0000009      0.0000010      Yes
0.00000001     0.50000000     0.00000000     0.00000001     0.00000000     Yes
0.00000002     0.25000000     0.00000000     0.00000001     0.00000000     Yes
0.00000003     0.50000000     0.00000002     0.00000001     0.00000002     Yes
0.00000005     0.10000000     0.00000000     0.00000001     0.00000000     Yes
0.00000005     0.30000000     0.00000002     0.00000001     0.00000002     Yes
0.00000005     0.50000000     0.00000002     0.00000003     0.00000002     Yes
0.00000005     0.70000000     0.00000004     0.00000003     0.00000004     Yes
0.00000005     0.90000000     0.00000004     0.00000005     0.00000004     Yes
0.00000006     0.25000000     0.00000002     0.00000001     0.00000002     Yes
0.00000009     0.50000000     0.00000004     0.00000005     0.00000004     Yes
0.00000010     0.05000000     0.00000000     0.00000001     0.00000000     Yes
0.00000010     0.15000000     0.00000002     0.00000001     0.00000002     Yes
0.00000010     0.25000000     0.00000002     0.00000003     0.00000002     Yes
0.00000010     0.35000000     0.00000004     0.00000003     0.00000004     Yes
0.00000010     0.45000000     0.00000004     0.00000005     0.00000004     Yes
0.00000010     0.65000000     0.00000006     0.00000007     0.00000006     Yes
0.00000010     0.85000000     0.00000008     0.00000009     0.00000008     Yes
0.00000010     0.95000000     0.00000010     0.00000009     0.00000010     Yes
0.000000001    0.500000000    0.000000000    0.000000001    0.000000000    Yes
0.000000002    0.250000000    0.000000000    0.000000001    0.000000000    Yes
0.000000005    0.100000000    0.000000000    0.000000001    0.000000000    Yes
0.000000005    0.500000000    0.000000002    0.000000003    0.000000002    Yes
0.000000005    0.900000000    0.000000004    0.000000005    0.000000004    Yes
0.000000006    0.750000000    0.000000004    0.000000005    0.000000004    Yes
0.000000009    0.500000000    0.000000004    0.000000005    0.000000004    Yes
0.000000010    0.050000000    0.000000000    0.000000001    0.000000000    Yes
0.000000010    0.250000000    0.000000002    0.000000003    0.000000002    Yes
0.000000010    0.450000000    0.000000004    0.000000005    0.000000004    Yes
0.000000010    0.650000000    0.000000006    0.000000007    0.000000006    Yes
0.000000010    0.850000000    0.000000008    0.000000009    0.000000008    Yes
0.000000010    0.950000000    0.000000010    0.000000009    0.000000010    Yes
0.0000000001   0.5000000000   0.0000000000   0.0000000001   0.0000000000   Yes
0.0000000002   0.2500000000   0.0000000000   0.0000000001   0.0000000000   Yes
0.0000000005   0.1000000000   0.0000000000   0.0000000001   0.0000000000   Yes
0.0000000005   0.5000000000   0.0000000002   0.0000000003   0.0000000002   Yes
0.0000000005   0.9000000000   0.0000000004   0.0000000005   0.0000000004   Yes
0.0000000006   0.7500000000   0.0000000004   0.0000000005   0.0000000004   Yes
0.0000000009   0.5000000000   0.0000000004   0.0000000005   0.0000000004   Yes
0.0000000010   0.0500000000   0.0000000000   0.0000000001   0.0000000000   Yes
0.0000000010   0.2500000000   0.0000000002   0.0000000003   0.0000000002   Yes
0.0000000010   0.4500000000   0.0000000004   0.0000000005   0.0000000004   Yes
0.0000000010   0.6500000000   0.0000000006   0.0000000007   0.0000000006   Yes
0.0000000010   0.8500000000   0.0000000008   0.0000000009   0.0000000008   Yes
0.00000000001  0.50000000000  0.00000000000  0.00000000001  0.00000000000  Yes
0.00000000002  0.25000000000  0.00000000000  0.00000000001  0.00000000000  Yes
0.00000000005  0.10000000000  0.00000000000  0.00000000001  0.00000000000  Yes
0.00000000005  0.50000000000  0.00000000002  0.00000000003  0.00000000002  Yes
0.00000000005  0.90000000000  0.00000000004  0.00000000005  0.00000000004  Yes
0.00000000006  0.75000000000  0.00000000004  0.00000000005  0.00000000004  Yes
0.00000000009  0.50000000000  0.00000000004  0.00000000005  0.00000000004  Yes
0.00000000010  0.05000000000  0.00000000000  0.00000000001  0.00000000000  Yes
0.00000000010  0.25000000000  0.00000000002  0.00000000003  0.00000000002  Yes
0.00000000010  0.45000000000  0.00000000004  0.00000000005  0.00000000004  Yes
0.00000000010  0.65000000000  0.00000000006  0.00000000007  0.00000000006  Yes
0.00000000010  0.85000000000  0.00000000008  0.00000000009  0.00000000008  Yes
0.000000000001 0.500000000000 0.000000000000 0.000000000001 0.000000000000 Yes
0.000000000002 0.250000000000 0.000000000000 0.000000000001 0.000000000000 Yes
0.000000000005 0.100000000000 0.000000000000 0.000000000001 0.000000000000 Yes
0.000000000005 0.300000000000 0.000000000002 0.000000000001 0.000000000002 Yes
0.000000000005 0.500000000000 0.000000000002 0.000000000003 0.000000000002 Yes
0.000000000005 0.700000000000 0.000000000004 0.000000000003 0.000000000004 Yes
0.000000000005 0.900000000000 0.000000000004 0.000000000005 0.000000000004 Yes
0.000000000006 0.750000000000 0.000000000004 0.000000000005 0.000000000004 Yes
0.000000000009 0.500000000000 0.000000000004 0.000000000005 0.000000000004 Yes
0.000000000010 0.050000000000 0.000000000000 0.000000000001 0.000000000000 Yes
0.000000000010 0.150000000000 0.000000000002 0.000000000001 0.000000000002 Yes
0.000000000010 0.250000000000 0.000000000002 0.000000000003 0.000000000002 Yes
0.000000000010 0.350000000000 0.000000000004 0.000000000003 0.000000000004 Yes
0.000000000010 0.450000000000 0.000000000004 0.000000000005 0.000000000004 Yes
0.000000000010 0.650000000000 0.000000000006 0.000000000007 0.000000000006 Yes
0.000000000010 0.850000000000 0.000000000008 0.000000000009 0.000000000008 Yes

Regards,
Bengt Richter



More information about the Python-list mailing list