# Workaround for the precision limitation for Python's round() function

I'm trying to come up with a general way to correctly round floats with Python, given the known limitation of round():

Note: The behavior of round() for floats can be surprising: for example, round(2.675, 2) gives 2.67 instead of the expected 2.68. This is not a bug: it’s a result of the fact that most decimal fractions can’t be represented exactly as a float. See Floating Point Arithmetic: Issues and Limitations for more information.

I thought that a simple way to correct for this effect is to add a small value to every float that ends with a 5, like so:

a = 7.85
b = a + 0.001
round(b, 1)
> 7.9


Below is the function I've written to do this. I've tested it and it seems to work fine, but I'd like some input on possible caveats I might be overlooking (or ways to improve it)

import numpy as np
from decimal import Decimal
from random import randint

def floats_5(N):
"""
Generate random floats between a random range, where all floats
end with a '5'.
"""
rang = randint(1, 10000)
flts = np.random.uniform(-1.*rang, 1.*rang, N)

# Add '5' to the end of each random float, with different lengths.
fl_5 = []
for f in flts:
# Trim float.
i = randint(2, len(str(f).split('.')[1]))
# Create trimmed float that ends with a '5' .
f = Decimal(str(f).split('.')[0] + '.' + str(f).split('.')[1][:i] +
'5')
fl_5.append(f)

return fl_5

def format_5(f):
"""
Take float and add a decimal '1' to the end of it.

Return the number of decimal paces and the new float.
"""
# Count number of decimal places.
n = len(str(f).split('.')[1])

# Decimal '1'  to add to the end of the float.
d = '0.' + '0'*n + '1'

# Add or subtract depending on the sign of the float.
c = -1. if str(f)[0] == '-' else 1.

# New augmented float.
new_float = f + Decimal(c)*Decimal(d)

# Return number of decimals and float with the small value added.
return n, new_float

# Get some random floats.
fl_5 = floats_5(10)

# Compare float, round(float), and round(new_float)
print 'Original   round(f)   round(new_f)'
for f in fl_5:
n, new_float = format_5(f)
print f, round(f, n-1), round(new_float, n-1), '\n'

• After you are satisfied with this code, please take a look at the decimal for actual production usage. – ferada Mar 8 '16 at 14:07
• Is there an equivalent of round(f, n) (where f is the float and n is the number of digits to round to) with Decimal? – Gabriel Mar 8 '16 at 14:11
• That's why I suggested to read it. Take a look at the quantize method and the description of rounding modes and the context. – ferada Mar 8 '16 at 14:17

This is a function that does what format_5() does but much succinctly:
def format_5_dec(f):

• And you can even return directly: return Decimal.quantize(f, Decimal(str(f)[:-1]), rounding='ROUND_UP') – Caridorc Mar 8 '16 at 19:05