Python’s round() Function Doesn’t do What You Think

How to avoid this simple mistake with round() in Python 3

Ryan Knightly
The Floating Point

--

I recently ran into a bug with my Python code that was the result of the last thing I would suspect: rounding. Here’s how to avoid making the same mistake.

The Problem

Basically, I expected the round() function to operate as rounding usually does, in that half numbers are rounded up to the next whole number. For example, round(4.5) should return 5 and round(5.5) should return 6.

Right?

Surprisingly, that is not how rounding works in Python. Rounding half numbers does not round up, and in fact, it doesn’t always round down either. Instead, it rounds to the nearest even number.

This behavior is shown by the following:

Python 3.7.3
>>> round(1.5)
2
>>> round(2.5)
2
>>> round(3.5)
4
>>> round(4.5)
4
>>> round(5.5)
6
>>> round(6.5)
6

It is worth pointing out that besides the half-number case, round() works as expected, in that it returns the nearest integer. For example, round(4.4) returns 4 and round(4.6) returns 5.

Note: This is only the case with Python 3. round() in Python 2 behaves as you would usually expect.

The Solution

We can fix this by defining a new rounding function:

def normal_round(number):
"""Round a float to the nearest integer."""
return int(number + 0.5)

Extended Solution

We can also take this a little further. The round() function also has another argument called ndigits that specifies how many digits to round to. For example, we can call round(3.14159, 2) and get 3.14 .

However, we run into a similar issue with rounding half values. Consider the following for example:

Python 3.7.3
>>> round(1.55, 1)
1.6
>>> round(1.65, 1)
1.6

To fix this, we can use a similar solution, and scale it to the specified number of decimal places with the following:

def normal_round(num, ndigits=0):
"""
Rounds a float to the specified number of decimal places.
num: the value to round
ndigits: the number of digits to round to
"""
if ndigits == 0:
return int(num + 0.5)
else:
digit_value = 10 ** ndigits
return int(num * digit_value + 0.5) / digit_value

The if-statement is there so that we get an int rather than a float when rounding to 0 digits. This means that rather than normal_round(4.2) returning 4.0 it will return 4.

I hope this helps!

--

--