Python: Numbers — Rounding Numbers

Python Rounding Numbers

Learning outcomes:

  • The round() function
  • Truncating numbers via trunc()
  • Flooring and ceiling numbers via floor() and ceil()
  • Rounding numbers in strings

Introduction

As common as it is to work with numbers in computing, is to round numbers, be it to given significant figures or decimal places, or to the nearest integer, nearest tens, nearest hundreds and so on.

In this chapter, we shall understand all aspects of rounding in Python. Specifically, we'll explore the global round() function and then the trunc(), floor() and ceil() functions of the math module.

Lastly, we'll consider a bit of string formatting in order to accomplish rounding to a given precision, reliably.

Let's start!

The round() function

The most basic way to round a given number to a given number of significant figures, or decimal places, is using the global round() function.

Shown below is the syntax of round():

round(number, precision)

The round() function takes in two arguments:

  1. The first argument number specifies the number to round, and is required.
  2. The second argument precision, which is optional, specifies the digit position at which you want your number to be rounded. The default value is 0 which implies the units digit. Positive values shift this position rightwards, while negative values shift it leftwards.

Let's first consider a few examples of round() without the first parameter:

round(4.2)
4
round(4.9)
5

round(4.2) rounds 4.2 to the nearest units giving 4. Similarly, round(4.9) rounds 4.9 to the nearest units giving 5.

Now let's bring on the second precision parameter.

It has to be an integer. A positive value shifts the digit position from the units digit to the right, while a negative value shifts the digit position to the left.

Let's first consider positive values:

round(3.14, 1)
3.1
round(20.636, 2)
20.64

As you can see here, round(3.14, 1) rounds 3.14 to the first digit after the units digit (and after the decimal point), that is highlighted as follows: 3.14. The result is 3.1, since the digit following 1 is lesser than 5.

To take in another way, round(3.14, 1) rounds 3.14 to 1 decimal place.

On the same lines, round(20.636, 2) rounds 20.636 to the second digit after the units digit, that is highlighted as follows: 20.636. The outcome of the rounding is 20.64, since the digit following 3 is greater than 5.

One important thing to note here is that since round() returns a number, one can't enforce a given decimal place precision on the final number.

Consider the following code:

Python
num = 2.5
print(round(num, 2))
2.5

We round the 2.5 to 2 decimal places and expect the result to be 2.50. However, we get back the same value 2.5. This is not an error with round()) — rather, it's a limitation arising from the internal representation of floats.

It just isn't possible to store the same float in two ways with a different number of ending zero digits.

So what to do in such cases? What if we want to enfore the rounding to 2, 3, or any number of decimal places?

For such cases, we have to use strings. We'll see this later in this chapter.

Anyways, let's now understand negative values to the precision parameter.

round(232.2, -1)
230.0
round(1687.5, -2)
1700.0

round(232.2, -1) rounds 232.2 to the first digit before the units digit, that is highlighted as follows: 232.2. The result is 230.0, since the digit following 3 is lesser than 5.

Now it's your turn to make intuition of the second statement here.

Note that in the snippet above, the results of the rounding expressions have a decimal point and a 0 at their end. This is because the return value of round() is of type float.

Anyways, if you have confusion in understanding the precision argument, the following explanation might be helpful.

Remembering the second argument to round()

One useful way of thinking about the second argument to round() is as follows:

Without any value, or with a value of 0, it rounds the given number to the units digit. Taking 35094.146 as a reference value, the highlighted number below showcases this position:

35094.146

Calling round(35094.146) (or equivalently round(35094.146, 0)) would return 35094.0.

A negative value shifts this position to the left, and a positive value shifts it to the right.

For example, -1 would change the highlighted position as shown below:

35094.146

Calling round(35094.146, -1) would return 35090.0.

Similarly, 1 would change the highlighted position as shown below:

35094.146

Calling round(35094.146, 1) would return 35094.1.

Truncation

Truncation is the name given to the process of removing the fractional part of a given number, if it exists. The fractional part is said to be truncated i.e cut off.

In Python, the math module provides a function to perform truncation of numbers. That is trunc().

Here is its syntax:

math.trunc(number)

As stated before, when given integers, the trunc() function returns them as is, since integers don't have a fractional part. But for floats, it cuts off their fractional part and returns the left off integer.

Let's truncate a couple of numbers:

import math
math.trunc(1.15)
1
math.trunc(-3.9)
-3
math.trunc(500)
500
math.trunc(-100.99)
-100
Restating it: when a float is truncated, its fractional part is chopped off.

Floor and ceil

In mathematics, there are two special functions that round off numbers to closest integers: ceil and floor.

The floor function rounds a number down to the largest closest integer. The ceil function, in contrast, rounds a number up to the smallest closest integer.

For instance, the floor of 3.2 is 3, whereas its ceil of 4. Similarly, the floor of 10.999 is 10, while its ceil is 11.

In Python, once again, the math module covers these two mathematical concepts in the functions floor() and ceil().

The floor() function floors its argument and returns the result. Similarly, the ceil() function ceils its argument and returns the result.

Shown below are a couple of examples using floor():

import math
math.floor(15.6)
15
math.floor(1.99999999)
1

And here we have a couple of examples using ceil():

import math
math.ceil(15.6)
16
math.ceil(0.000001)
1
math.ceil(-10.2)
-10

Difference between trunc() and floor()

At first sight, one might think that floor() and trunc() do exactly the same thing, however the reality is that they don't!

They only do the same thing for positive numbers. For negative numbers this doesn't hold. A simple example can illustrate why:

The floor of -2.13 would be the largest integer smaller than -2.13, and that is -3. However, truncating -2.13 would give -2, throwing away .13. For negative numbers, trunc() doesn't floor the number, but rather ceils them.

Rounding in strings

Let's now get back to our old problem of enforcing a given amount of decimal places on a given number. As we've seen above, doing so is not always possible using the round() function.

To solve this problem, we need string formatting. The string method format() formats a string by filling out given places in the string with given values (passed as arguments to the method).

Now, we won't go in detail of string formatting in this chapter - it's to be covered separately in the Python Strings Basics chapter.

For now, we'll just see how to use it for the purposes of rounding numbers.

Suppose we want to round 2.1 to 3 d.p. to give 2.100. This is how it'll be done using format():

Python
num = '{:.3f}'.format(2.1)
print(num)
'2.100'

First a string is written — format() is a string method so obviously we have to start with a string. Then inside the string where ever we write a pair of curly braces, format() will replace these with a given value.

Then comes the format() method with the argument 2.1. format() takes in arguments and puts them in corresponding places in the given string.

These places are denoted by a pair of curly braces. Wherever the first such pair occurs, the method replaces it with its first argument; wherever the second occurs, it's replaced with the second argument; and so on and so forth.

Inside the pair of curly braces, we can optionally even define how the replacing value should be formatted. For example, using the notation {:.nf} we can get the provided number to be written with a given number of decimal digits.

In our case, {:.3f} converts 2.1 to the string '2.100', enforcing the extra zeroes at the end.

Similarly, {:.5f} would show 2.1 as '2.10000', enforcing 5 digits after the decimal point.