Rounding in Python
Recently we released a post on the floor division and modulo operators, where we highlighted some potentially unexpected behaviour when using these operators with negative numbers. The source of this unexpected behaviour turned out to be the floor
function: one of several rounding functions defined in the math
module.
In this post I want to go into a little more detail about these rounding functions, in addition to the built-in round
function, and highlight some things you should be aware of when using them.
Floor
The floor
function in the math module takes in a non-complex number as an argument and returns this value rounded down as an integer. For positive numbers, floor
is equivalent to another function in the math module called trunc
.
>>> floor(3.1)
3
>>> trunc(3.1)
3
However, the behaviour of floor
and trunc
begins to diverge when we pass in negative numbers as arguments.
>>> floor(-3.1)
-4
>>> trunc(-3.1)
-3
So why does floor(-3.1)
return -4
? The answer can be found in the Python documentation for the math module.
Return the floor of x, the largest integer less than or equal to x.
So floor(-3.1)
returns -4
because -4
is the largest number less than -3.1
. It is further left along the number line.
trunc
on the other hand is simply truncating the value we provide as an argument, throwing away everything after the decimal point.
The important takeaway here, is that floor
rounds towards zero for positive numbers, and away from zero for negative numbers.
Ceil
The ceil
function is the opposite of floor
.
Return the ceiling of x, the smallest integer greater than or equal to x.
For positive numbers, ceil
rounds away from zero.
>>> ceil(3.1)
4
>>> ceil(5.7)
6
But for negative numbers, ceil
rounds towards zero.
>>> ceil(-3.1)
-3
>>> ceil(-5.7)
-5
In the above example, -3
is greater than -3.1
. It's closer to zero.
Round
Unlike ceil
, floor
, and trunc
, round
can be found in the standard library as a built-in function. The documentation can be found here.
I think with round, it's best to look at a few examples first.
>>> round(3.5)
4
>>> round(2.5)
2
>>> round(2.51)
3
>>> round(-3.5)
-4
>>> round(-2.5)
-2
Okay, so the first thing to notice is that round
, unlike ceil
and floor
provides the same result for positive and negative numbers, just with the opposite sign. There is something fishy going on, however. 2.5
rounded to 2
(rounding down), but 3.5
rounded to 4
(rounding up). What's going on?
Bankers' rounding
As it turns out, round
implements a type of rounding called bankers' rounding. So, what is bankers' rounding? And why does it exist?
In most cases, bankers' rounding works as we might expect. It rounds to the closest significant figure. So 0.346
rounded to two decimal places yields 0.35
. 5.3
rounded to the nearest integer yields 5
. Bankers' rounding is special in how it deals with ties.
In the event of a tie, for example 3.5
, which is equally close to 3
and 4
, bankers' rounding always rounds towards the closest even number. 3.5
therefore rounds towards 4
, but 2.5
rounds towards 2
, as 2.5
is much closer to 2
than 4
.
Why would we want to do this kind of rounding?
The main reason is that for large sets of numbers, bankers' rounding is less biased, so a series of additions and subtractions with rounded numbers more accurately represents the true total of the unrounded numbers. We can easily imagine a case with a large amount of positive numbers get rounded up, therefore inflating the final result. Using bankers' rounding, many of those numbers would instead be rounded down, balancing those that were rounded up, and producing a more accurate result.
Recap
floor
always rounds towards zero for positive numbers, but away from zero for negative numbers.3.1
therefore rounds to3
usingfloor
, but-3.1
rounds to-4
.ceil
is the opposite offloor
, andceil
always rounds away from zero for positive numbers, but towards zero for negative numbers.trunc
performs truncation, returning the integer portion of a given number.round
implements a type of rounding called bankers' rounding. In bankers' rounding, we round towards the closest significant figure, except in the case of a tie. In the event of a tie, we round towards the closest even significant figure. So3.5
rounds to4
, but2.5
rounds to2
.
I hope you learnt something new, and if you're looking to upgrade your Python skills even further, you might want to check out our Complete Python Course.