Python Notes: Fun with Floats and Decimals

  1. The information presented here is intended for educational use.
  2. The information presented here is provided free of charge, as-is, with no warranty of any kind.
  3. 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.