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 to 3 using floor, but -3.1 rounds to -4.
  • ceil is the opposite of floor, and ceil 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. So 3.5 rounds to 4, but 2.5 rounds to 2.

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.