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)
&gt; 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'
Following ferada's advice I looked up the decimal module and it simplifies things quite a bit.

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

def format_5_dec(f):
    new_float = Decimal.quantize(f, Decimal(str(f)[:-1]), rounding='ROUND_UP')
    return new_float