[python] Round float to x decimals?

Is there a way to round a python float to x decimals? For example:

>>> x = roundfloat(66.66666666666, 4)
66.6667
>>>x = roundfloat(1.29578293, 6)
1.295783

I've found ways to trim/truncate them (66.666666666 --> 66.6666), but not round (66.666666666 --> 66.6667).

This question is related to python math rounding

The answer is


I coded a function (used in Django project for DecimalField) but it can be used in Python project :

This code :

  • Manage integers digits to avoid too high number
  • Manage decimals digits to avoid too low number
  • Manage signed and unsigned numbers

Code with tests :

def convert_decimal_to_right(value, max_digits, decimal_places, signed=True):

    integer_digits = max_digits - decimal_places
    max_value = float((10**integer_digits)-float(float(1)/float((10**decimal_places))))

    if signed:
        min_value = max_value*-1
    else:
        min_value = 0

    if value > max_value:
        value = max_value

    if value < min_value:
        value = min_value

    return round(value, decimal_places)


value = 12.12345
nb = convert_decimal_to_right(value, 4, 2)
# nb : 12.12

value = 12.126
nb = convert_decimal_to_right(value, 4, 2)
# nb : 12.13

value = 1234.123
nb = convert_decimal_to_right(value, 4, 2)
# nb : 99.99

value = -1234.123
nb = convert_decimal_to_right(value, 4, 2)
# nb : -99.99

value = -1234.123
nb = convert_decimal_to_right(value, 4, 2, signed = False)
# nb : 0

value = 12.123
nb = convert_decimal_to_right(value, 8, 4)
# nb : 12.123

The Mark Dickinson answer, although complete, didn't work with the float(52.15) case. After some tests, there is the solution that I'm using:

import decimal
        
def value_to_decimal(value, decimal_places):
    decimal.getcontext().rounding = decimal.ROUND_HALF_UP  # define rounding method
    return decimal.Decimal(str(float(value))).quantize(decimal.Decimal('1e-{}'.format(decimal_places)))

(The conversion of the 'value' to float and then string is very important, that way, 'value' can be of the type float, decimal, integer or string!)

Hope this helps anyone.


Default rounding in python and numpy:

In: [round(i) for i in np.arange(10) + .5]
Out: [0, 2, 2, 4, 4, 6, 6, 8, 8, 10]

I used this to get integer rounding to be applied to a pandas series:

import decimal

and use this line to set the rounding to "half up" a.k.a rounding as taught in school: decimal.getcontext().rounding = decimal.ROUND_HALF_UP

Finally I made this function to apply it to a pandas series object

def roundint(value):
    return value.apply(lambda x: int(decimal.Decimal(x).to_integral_value()))

So now you can do roundint(df.columnname)

And for numbers:

In: [int(decimal.Decimal(i).to_integral_value()) for i in np.arange(10) + .5]
Out: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Credit: kares


I feel compelled to provide a counterpoint to Ashwini Chaudhary's answer. Despite appearances, the two-argument form of the round function does not round a Python float to a given number of decimal places, and it's often not the solution you want, even when you think it is. Let me explain...

The ability to round a (Python) float to some number of decimal places is something that's frequently requested, but turns out to be rarely what's actually needed. The beguilingly simple answer round(x, number_of_places) is something of an attractive nuisance: it looks as though it does what you want, but thanks to the fact that Python floats are stored internally in binary, it's doing something rather subtler. Consider the following example:

>>> round(52.15, 1)
52.1

With a naive understanding of what round does, this looks wrong: surely it should be rounding up to 52.2 rather than down to 52.1? To understand why such behaviours can't be relied upon, you need to appreciate that while this looks like a simple decimal-to-decimal operation, it's far from simple.

So here's what's really happening in the example above. (deep breath) We're displaying a decimal representation of the nearest binary floating-point number to the nearest n-digits-after-the-point decimal number to a binary floating-point approximation of a numeric literal written in decimal. So to get from the original numeric literal to the displayed output, the underlying machinery has made four separate conversions between binary and decimal formats, two in each direction. Breaking it down (and with the usual disclaimers about assuming IEEE 754 binary64 format, round-ties-to-even rounding, and IEEE 754 rules):

  1. First the numeric literal 52.15 gets parsed and converted to a Python float. The actual number stored is 7339460017730355 * 2**-47, or 52.14999999999999857891452847979962825775146484375.

  2. Internally as the first step of the round operation, Python computes the closest 1-digit-after-the-point decimal string to the stored number. Since that stored number is a touch under the original value of 52.15, we end up rounding down and getting a string 52.1. This explains why we're getting 52.1 as the final output instead of 52.2.

  3. Then in the second step of the round operation, Python turns that string back into a float, getting the closest binary floating-point number to 52.1, which is now 7332423143312589 * 2**-47, or 52.10000000000000142108547152020037174224853515625.

  4. Finally, as part of Python's read-eval-print loop (REPL), the floating-point value is displayed (in decimal). That involves converting the binary value back to a decimal string, getting 52.1 as the final output.

In Python 2.7 and later, we have the pleasant situation that the two conversions in step 3 and 4 cancel each other out. That's due to Python's choice of repr implementation, which produces the shortest decimal value guaranteed to round correctly to the actual float. One consequence of that choice is that if you start with any (not too large, not too small) decimal literal with 15 or fewer significant digits then the corresponding float will be displayed showing those exact same digits:

>>> x = 15.34509809234
>>> x
15.34509809234

Unfortunately, this furthers the illusion that Python is storing values in decimal. Not so in Python 2.6, though! Here's the original example executed in Python 2.6:

>>> round(52.15, 1)
52.200000000000003

Not only do we round in the opposite direction, getting 52.2 instead of 52.1, but the displayed value doesn't even print as 52.2! This behaviour has caused numerous reports to the Python bug tracker along the lines of "round is broken!". But it's not round that's broken, it's user expectations. (Okay, okay, round is a little bit broken in Python 2.6, in that it doesn't use correct rounding.)

Short version: if you're using two-argument round, and you're expecting predictable behaviour from a binary approximation to a decimal round of a binary approximation to a decimal halfway case, you're asking for trouble.

So enough with the "two-argument round is bad" argument. What should you be using instead? There are a few possibilities, depending on what you're trying to do.

  • If you're rounding for display purposes, then you don't want a float result at all; you want a string. In that case the answer is to use string formatting:

    >>> format(66.66666666666, '.4f')
    '66.6667'
    >>> format(1.29578293, '.6f')
    '1.295783'
    

    Even then, one has to be aware of the internal binary representation in order not to be surprised by the behaviour of apparent decimal halfway cases.

    >>> format(52.15, '.1f')
    '52.1'
    
  • If you're operating in a context where it matters which direction decimal halfway cases are rounded (for example, in some financial contexts), you might want to represent your numbers using the Decimal type. Doing a decimal round on the Decimal type makes a lot more sense than on a binary type (equally, rounding to a fixed number of binary places makes perfect sense on a binary type). Moreover, the decimal module gives you better control of the rounding mode. In Python 3, round does the job directly. In Python 2, you need the quantize method.

    >>> Decimal('66.66666666666').quantize(Decimal('1e-4'))
    Decimal('66.6667')
    >>> Decimal('1.29578293').quantize(Decimal('1e-6'))
    Decimal('1.295783')
    
  • In rare cases, the two-argument version of round really is what you want: perhaps you're binning floats into bins of size 0.01, and you don't particularly care which way border cases go. However, these cases are rare, and it's difficult to justify the existence of the two-argument version of the round builtin based on those cases alone.


Examples related to python

programming a servo thru a barometer Is there a way to view two blocks of code from the same file simultaneously in Sublime Text? python variable NameError Why my regexp for hyphenated words doesn't work? Comparing a variable with a string python not working when redirecting from bash script is it possible to add colors to python output? Get Public URL for File - Google Cloud Storage - App Engine (Python) Real time face detection OpenCV, Python xlrd.biffh.XLRDError: Excel xlsx file; not supported Could not load dynamic library 'cudart64_101.dll' on tensorflow CPU-only installation

Examples related to math

How to do perspective fixing? How to pad a string with leading zeros in Python 3 How can I use "e" (Euler's number) and power operation in python 2.7 numpy max vs amax vs maximum Efficiently getting all divisors of a given number Using atan2 to find angle between two vectors How to calculate percentage when old value is ZERO Finding square root without using sqrt function? Exponentiation in Python - should I prefer ** operator instead of math.pow and math.sqrt? How do I get the total number of unique pairs of a set in the database?

Examples related to rounding

How to round a numpy array? How to pad a string with leading zeros in Python 3 Python - round up to the nearest ten How to round a Double to the nearest Int in swift? Using Math.round to round to one decimal place? How to round to 2 decimals with Python? Rounding to 2 decimal places in SQL Rounding to two decimal places in Python 2.7? Round a floating-point number down to the nearest integer? Rounding BigDecimal to *always* have two decimal places