Python Notes: Fun with Floats and Decimals
- The information presented here is intended for educational use.
- The information presented here is provided free of charge, as-is, with no warranty of any kind.
- Edit: 2022-05-21
Fun with Floats and Decimals
#!/bin/python3
# ======================================================
# title : fun_with_floats6.py
# author : Neil Rieck
# created: 2202-05-22
# edit : 2202-05-25
# notes :
# 1) current environment: Python 3.6.8
# 2) decimal.Decimal is more accurate than float
# 3) be careful how you initialize decimals (this
# also applies to floats)
# 4) d1 fails because (0.12) first creats a float
# 5) a quoted string always produces the best conversion
# ======================================================
#
from decimal import Decimal, ROUND_HALF_UP, getcontext, \
FloatOperation
D = Decimal # a shortcut to save keystrokes
# -------------------------------------------
# a little routine to create python varibles the
# same way you might have typed them in.
# eg.
# f1 = 0.12
# f2 = (0.12)
# f3 = ('0.12')
# d1 = D(0.12)
# d2 = D('0.12')
# -------------------------------------------
def convert_n_display(testdata):
# start of python3 security workaround
ldict = {} # local xfer area
py_code = (f"f1 = {testdata}\n"
f"f2 = ({testdata})\n"
f"f3 = ('{testdata}')\n"
f"d1 = D({testdata})\n"
f"d2 = D('{testdata}')")
# print(py_code)
exec(py_code, globals(), ldict)
f1 = ldict['f1']
f2 = ldict['f2']
f3 = ldict['f3']
d1 = ldict['d1']
d2 = ldict['d2']
# end of python3 security workaround
print(f"\ndata: {testdata}")
print(f" f1: {f1}")
print(f" f2: {f2}")
print(f" f3: {f3}")
print(f" d1: {d1}")
print(f" d2: {d2}")
return
# -------------------------------------------
# a little routine to display stuff
# -------------------------------------------
def display5(test, a, b, c, msg=""):
temp = f"\ntest: {test}"
if msg != "":
temp += f" ({msg})"
print(temp)
print(f" a: {a}")
print(f" b: {b}")
print(f" c: {c}")
return
# ====================================================
print("\nfun with floats6.py")
#
# my problems began while attempting to port some
# financial routines from another language into
# Python
#
print("\npart 1: gaap rounding")
try:
print("test 1a (this will fail)")
data1 = D(0.155555)
cents = D(.01) # this will throw a quantize error
rslt1 = data1.quantize(cents, ROUND_HALF_UP)
display5('1a', data1, cents, rslt1, "this will fail")
except Exception as e:
print(f'runtime error: {e}')
#
print("test 1b (produces the expected result)")
data1 = D('0.1555551')
cents = D('.01')
rslt1 = data1.quantize(cents, ROUND_HALF_UP)
display5('1b', data1, cents, rslt1, "works as expected")
#
# your variable initialize technique is more important
# in python than I previously expected
#
print("\npart 2: testing initialization")
convert_n_display('0.01')
convert_n_display('0.01234567890')
#
# protecting yourself from unintentional mixing of
# floats with Decimals (includes initialization)
#
print("\npart 3: trap unintentional mixing")
c = getcontext()
c.traps[FloatOperation] = True
try:
data1 = D('0.1555551') # this will work
cents = D(.01) # this will fail
# these 2 lines will never run
rslt1 = data1.quantize(cents, ROUND_HALF_UP)
display5('1b', data1, cents, rslt1, "as expected")
except Exception as e:
print(f"runtime error: {e}")
#
print("\npart 4: increasing digits of precision")
getcontext().prec = 999 # let's go crazy
dividend8 = D('1.0')
divisor8 = D('3.0')
result8 = dividend8 / divisor8
display5('4a', dividend8, divisor8, result8, "999 digits")
#
getcontext().prec = 9999
result8 = dividend8 / divisor8
display5('4b', dividend8, divisor8, result8, "9999 digits")
#
# end
fun with floats6.py
part 1: gaap rounding
test 1a (this will fail)
runtime error: [<class 'decimal.InvalidOperation'>]
test 1b (produces the expected result)
test: 1b (works as expected)
a: 0.1555551
b: 0.01
c: 0.16
part 2: testing initialization
data: 0.01
f1: 0.01
f2: 0.01
f3: 0.01
d1: 0.01000000000000000020816681711721685132943093776702880859375
d2: 0.01
data: 0.01234567890
f1: 0.0123456789
f2: 0.0123456789
f3: 0.01234567890
d1: 0.0123456789000000004274948395277533563785254955291748046875
d2: 0.01234567890
part 3: trap unintentional mixing
runtime error: [<class 'decimal.FloatOperation'>]
part 4: increasing digits of precision
test: 4a (999 digits)
a: 1.0
b: 3.0
c: 0.3333333333333333333333333333333333333333333333333333333333
333333333333333333333333333333333333333333333333333333333333333333
333333333333333333333333333333333333333333333333333333333333333333
333333333333333333333333333333333333333333333333333333333333333333
333333333333333333333333333333333333333333333333333333333333333333
333333333333333333333333333333333333333333333333333333333333333333
333333333333333333333333333333333333333333333333333333333333333333
333333333333333333333333333333333333333333333333333333333333333333
333333333333333333333333333333333333333333333333333333333333333333
333333333333333333333333333333333333333333333333333333333333333333
333333333333333333333333333333333333333333333333333333333333333333
333333333333333333333333333333333333333333333333333333333333333333
333333333333333333333333333333333333333333333333333333333333333333
333333333333333333333333333333333333333333333333333333333333333333
333333333333333333333333333333333333333333333333333333333333333333
33333333333333333
test: 4b (9999 digits)
a: 1.0
b: 3.0
c: 0.3333333333333333333333333333333333333333333333333333333333
{ remaining text snipped out but works }
Links
Back to
Home
Neil Rieck
Waterloo, Ontario, Canada.