This tutorial on Rounding Numbers in Python explains different methods to round numbers with multiple programming code examples:
Computers were originally invented for complex math operations and we didn’t bother as long as integers, subtraction, addition, and multiplication were involved. However, things became unpredictable when floating points and division got involved.
One of the reasons for this unpredictability of floating points or division is mentioned in Python’s documentation. We humans mostly work with the decimal system, which has base 10. However, the computer uses and stores the binary system(0s and 1s).
=> Check ALL Python Tutorials Here
Coming back to Python’s documentation, this is what it has to say;
Unfortunately, most decimal fractions cannot be represented exactly as binary fractions. A consequence is that, in general, the decimal floating-point numbers you enter are only approximated by the binary floating-point numbers actually stored in the machine.
FYI, this is in the very nature of binary floating-point and not a bug in Python. So most, if not all programming languages like Perl, C, C++, Java, Fortran often won’t display the exact decimal number you expect.
Suggested Reading =>> Python Built-in Data Types- None and Numeric
Table of Contents:
Rounding Numbers in Python
In this article, we shall learn the various ways of rounding numbers in Python.
Floating-Point Numbers
This article is all about rounding numbers. But which numbers? In this section, we are going to have a quick look at these numbers.
A floating-point number is basically any number with a decimal point dividing the integer from the fractional parts. Represented as a 64-bit double-precision in Python, it has the data type ‘float’ and can also be represented in scientific notations, with e or E indicating the power 10. For example (1.4E4 = 1.4 x 104 = 14000)
In the introduction, we mentioned that most decimal fractions can’t be represented exactly as binary fractions. Let’s take a look at the example below.
Example 1: Unpredictability of floating-point numbers
>>> 0.1 + 0.1 + 0.1 # expected 0.3 0.30000000000000004 >>> 1/10 0.1 >>> 1/10 == 0.10000000000000001 # expected False True >>> format(0.1, '.17f') # expland to 17 decimal places '0.10000000000000001'
From the example above, the first line of code(addition of 0.1s) didn’t return the expected result of 0.3. Also, we see that 1/10 equals 0.1, but surprisingly, the equality operator in the third line of code returned True. However, the last line of code reveals the real value of 0.1 in 17 decimal places.
The simple reason is that both results are just the nearest approximate binary fractions. And interestingly, in Python, there are many different decimal numbers that share the same binary fraction. For example, the numbers 0.1 and 0.10000000000000001 as we saw above.
This example above shows us how floating-point numbers are inaccurate and thus tricky to work with. This is where rounding numbers can spare us a lot by minimizing computing errors.
Rounding Numbers
Wikipedia defines rounding to be the replacement of a number with an approximate value that has a shorter, simpler, or more explicit representation. For example, replacing 4.345 with 4.3, 3.67 with 4.
Still from Wikipedia, it is mentioned that rounding is often done to obtain a value that is easier to report and communicate than the original. But this usually comes with round-off errors.
Most of us were taught two simple rules to apply when rounding numbers. If the digit after the decimal digit we are aiming for is between 0 and 4 inclusively, round down(add 0), if it is between 5 and 9 inclusively, round up(add 1).
Check the diagram below for a demonstration:
Say we have a floating-point number 3.45678 and we want to round it to n decimal places, in this case, 3. From the diagram above, we have 6 as our target decimal number because shifting the decimal point 3 times to the right will be 346.78. The number after the decimal point, in this case, is 7, which falls between 5 and 9. Hence, we round up by adding 1 to 6.
Finally, we discard all numbers after the decimal point and bring them back to their normal position, which gives us 3.457
One of the built-in functions provided by Python to handle rounding numbers is the round() function (more details below).
Built-in Python round() Function
The round() function is a Python built-in function that takes in two numeric arguments, number and an optional ndigit, then returns the number, n rounded to a given precision in decimal digits, ndigit.
In other words, it rounds the number to the closest multiple of 10-ndigits
Syntax:
round(number, [, ndigit])
- If ndigit is omitted or None, the return value will be the nearest integer.
- ndigit can be negative, which may not give us the results we had in mind.
Let’s now see an example that shows the various ways the round() function can be used:
Example 2: Using the round() function
def rounding_numbers(): #ex_1 ndigits is None, which is same as no ndigit given number = 1.5 ndigits = None print("Round {} to {} ndigits: {}".format(number, ndigits, round(number, ndigits))) #ex_2 no ndigits is given, which is same as ndigits = None number = 2.5 print("Round {}: {}".format(number, round(number))) #ex_3 round to 3 decimal places number = 3.45678 ndigits = 3 print("Round {} to {} ndigits: {}".format(number, ndigits, round(number, ndigits))) #ex_4 round to zero decimal places number = 35.45678 ndigits = 0 print("Round {} to {} ndigits: {}".format(number, ndigits, round(number, ndigits))) #ex_5 round to a nagative decimal place number = 12.56 ndigits = -1 print("Round {} to {} ndigits: {}".format(number, ndigits, round(number, ndigits))) if __name__ == '__main__': rounding_numbers()
Output:
In ex_1 above, we defined our ndigits to be None, which is the same as no ndigits at all.
In ex_2, we can see that rounding 2.5 to the nearest integer gives 2 instead of the expected 3.
This is because, as seen in the diagram above, 2.5 is at the middle of the two closest integers, which are; 3 and 2
Since there is no closest integer here, Python uses the IEEE 754 standard called banker’s rounding, which rounds to the nearest least significant even digit. So, between 3 and 2, we have 2 as the least significant even digit.
In ex_4, with ndigits of 0, all digits after the decimal point are replaced with a single zero. This happens regardless of how many digits appear after the decimal point. It is the same as passing None or no ndigits at all. The only difference is that with a ndigits of zero, the result returned is float.
In ex_5, we are rounding to a negative decimal place. Since ndigits is -1, it will round the number to the closest multiple of 10-(-1) = 10.
From the diagram above, 12.56 is in between the two multiples; 10 and 20, but closest to 10.
In ex_6, the result is not as expected. The expected value is 2.68 instead of 2.67. We shall see later that this is one of the areas where the Decimal module outshines the round() function.
Rounding With Math Module
Over the years, mathematicians have developed an awful lot of rounding methods in order to address the problem of rounding. In this section, we shall go through a few of the commonly used methods.
#1) Truncation
This is the simplest method of rounding numbers. Here, we simply replace every digit after the position we want to keep with zeros.
The math library has the trunc() function that can truncate floating-point values. However, it can only truncate to whole numbers and doesn’t accept an argument for the number of decimal places to keep.
In the example below, we shall customize this function so that it can take into consideration the number of decimal places.
Example 3: Customized truncate() function that takes an argument for decimal places to keep
import math def truncate(number, decimal=0): tens = 10.0 if not isinstance(decimal, int): raise TypeError("Argument 'decimal' must be of type int") if decimal == 0: return math.trunc(number) multiples = math.pow(tens, decimal) # tens ** decimal return math.trunc(number * multiples) / multiples if __name__ == '__main__': number = 435.3387743 decimal_list = [0, 2, 3, 4, -1, -2] for decimal in decimal_list: print("Truncate {} to {} decimal: {}".format(number, decimal, truncate(number, decimal)))
Output:
In summary, if the decimal argument is provided and is valid(not float), we first multiply the number with 10.0n, where n is the decimal. Then, we truncate it using the math.trunc() function. Finally, we divide it with the same 10.0n. The decimal, n can be negative to truncate the digits to the left of the decimal point.
#2) Rounding Up
This technique always rounds a number up to a number of digits greater than or equal to the given number.
The Python math module has the ceil() function that rounds a number to the nearest integer greater than or equal to the given number. This function only takes in one argument. However, just as in example 3, we shall build a customized function that takes in the number of decimal places to keep.
Example 4: Customized rounding_up() function that takes an argument for decimal places to keep
import math def rounding_up(number, decimal=0): tens = 10.0 if not isinstance(decimal, int): raise TypeError("Argument 'decimal' must be of type int") if decimal == 0: return math.ceil(number) multiples = math.pow(tens, decimal) # tens ** decimal return math.ceil(number * multiples) / multiples if __name__ == '__main__': numbers = [1.2, 1.534, 33.2, 3356, -1.6] decimal_list = [0, 1, -1, -2, 0] for decimal, number in zip(decimal_list, numbers): print("Round up {} to {} decimal: {}".format(number, decimal, rounding_up(number, decimal)))
Output:
In the output above, rounding up 1.2 gives 2 because the closest integers to 1.2 are 1 and 2 and 2 happens to be the greatest. A negative decimal input rounds up the number to the left of the decimal point. Rounding up -1.6 to 0 decimal place gives -1 because the closest integers are -1 and –2, and -1 happens to be the greatest.
#3) Rounding Down
This technique always rounds a number down to a number of digits smaller than or equal to the given number.
The Python math module has the floor() function that rounds a number to the nearest integer, smaller than or equal to the given number. Just as ceil(), this function doesn’t allow us to specify the number of decimal places to keep.
Example 5: Customized rounding_down() function that takes an argument for decimal places to keep
import math def rounding_down(number, decimal=0): tens = 10.0 if not isinstance(decimal, int): raise TypeError("Argument 'decimal' must be of type int") if decimal == 0: return math.floor(number) multiples = math.pow(tens, decimal) # tens ** decimal return math.floor(number * multiples) / multiples if __name__ == '__main__': numbers = [1.2, 1.534, 33.2, 3356, -1.6] decimal_list = [0, 1, -1, -2, 0] for decimal, number in zip(decimal_list, numbers): print("Round down {} to {} decimal: {}".format(number, decimal, rounding_down(number, decimal)))
Output:
#4) Rounding Half Up
This strategy is the common method of rounding, which is the same as rounding up In addition, it breaks ties by rounding up. That is, half-way values of x are always rounded up.
In this strategy, one digit is checked to determine the rounding direction. After shifting the decimal point by 10-n as we did for the above examples, we investigate the digit after the shifted decimal point to know if it is less than or greater than or equal to 5.
This is mostly done by adding 0.5 to the shifted value, which will add a 1 to the integer part of the shifted value if greater than 0.5. Then we use the floor() function to get the largest integer.
Example 6: Rounding half up or Round half toward positive infinity.
import math def rounding_half_up(number, decimal=0): tens = 10.0 half_way = 0.5 if not isinstance(decimal, int): raise TypeError("Argument 'decimal' must be of type int") if decimal == 0: return math.floor(number + half_way) multiples = math.pow(tens, decimal) # tens ** decimal return math.floor(number * multiples + half_way) / multiples if __name__ == '__main__': numbers = [7.6, 7.5, 7.4, 1.23, 1.28, 1.25, -1.225] decimal_list = [0,0,0,1,1,1,2] for decimal, number in zip(decimal_list, numbers): print("Round half up {} to {} decimal: {}".format(number, decimal, rounding_half_up(number, decimal)))
Output:
#5) Rounding Half Down
This strategy is the opposite of Rounding Half Up. It breaks ties by rounding to the lesser of the two ties. In short, it makes 0.5 go down.
To achieve this, the modification we have to do to the rounding_half_up() in example 6 is to change the floor() function to ceil(). Then we subtract 0.5 instead of adding.
Example 7: Rounding half down
import math def rounding_half_down(number, decimal=0): tens = 10.0 half_way = 0.5 if not isinstance(decimal, int): raise TypeError("Argument 'decimal' must be of type int") if decimal == 0: return math.ceil(number - half_way) multiples = math.pow(tens, decimal) # tens ** decimal return math.ceil(number * multiples - half_way) / multiples if __name__ == '__main__': numbers = [7.6, 7.5, 7.4, 1.23, 1.28, 1.25, -1.225] decimal_list = [0,0,0,1,1,1,2] for decimal, number in zip(decimal_list, numbers): print("Round half down {} to {} decimal: {}".format(number, decimal, rounding_half_down(number, decimal)))
Output:
#6) Rounding Half To Even
This strategy breaks ties by rounding to the nearest least even number. In short, it rounds 0.5 to the nearest even digit.
This strategy is used by the built-in round() function to break ties and it is the default rounding rule in the IEEE-754 standard. We already explained why rounding 2.5 returned 2 instead of 3 in example 2 above.
Rounding With Decimal Module
The Python Decimal module has several benefits over the round() built-in. It has an exact decimal representation. We saw in example 1, the unpredictability of floating-point numbers, where 0.1 + 0.1 + 0.1 == 0.3 returned False. With the Decimal module, the expected value is returned.
Example 8: Exact decimal representation
>>> from decimal import Decimal >>> Decimal('0.1') + Decimal('0.1') + Decimal('0.1') Decimal('0.3')
Let’s examine the default context of the decimal module by invoking its .getcontext() method.
Example 9: Viewing the current context of the Decimal module
>>> import decimal >>> decimal.getcontext() Context(prec=1, rounding=ROUND_HALF_EVEN, Emin=-999999, Emax=999999, capitals=1, clamp=0, flags=[Inexact, Rounded], traps=[InvalidOperation, DivisionByZero, Overflow])
We can notice that the default rounding strategy used is rounding half even. Also, the default precision is 26 digits. However, note that these values can be altered to suit our needs.
Rounding a decimal is done with the .quantize() method. This method takes in a decimal value, representing the number of decimal places to keep.
Example 10: Rounding with the Decimal module
>>> from decimal import Decimal >>> d = Decimal('2.675') >>> d.quantize(Decimal('1.00')) # round to 2 decimal places Decimal('2.68')
We can remember in example 2 that rounding 2.675 to 2 decimal places with the round() function failed by returning 2.67 instead of 2.68.
As mentioned earlier, we can alter the values of the precision attribute and rounding strategy to one of the following flags:
Table 1: Flags for rounding strategy
Flag | Description |
---|---|
ROUND_CEILING | Round towards Infinity or rounding up. |
ROOUND_DOWN | Round towards zero or truncate. |
ROUND_FLOOR | Round towards -Infinity or rounding down. |
ROUND_HALF_DOWN | Round to nearest with ties going towards zero. |
ROUND_HALF_EVEN | Round to nearest with ties going to nearest even integer. |
ROUND_HALF_UP | Round to the nearest number and break ties by rounding up. |
ROUND_UP | Round away from zero. |
ROUND_05UP | Round up if the last digit is 0 or 5, otherwise round down. |
These flags may not mean the same thing as we saw at the beginning of this article. For example, ROUND_CEILING works like our round_up() function while the ROUND_FLOOR works like our round_down() function.
Also, both ROUND_FLOOR and ROUND_CEILING are not symmetric around zero, unlike both ROUND_UP and ROUND_DOWN.
ROUND_DOWN works like our truncate() function by rounding everything towards zero.
Let’s see the first flag in action.
Example 11: Rounding up with decimal.ROUND_CEILING
import decimal def decimal_ceil(number, dec=0): return decimal.Decimal(str(number)).quantize(decimal.Decimal(str(dec))) if __name__ == '__main__': # set rounding strategy decimal.getcontext().rounding = decimal.ROUND_CEILING numbers = [1.2, 1.534, -1.6] decimal_list = [0, 1., 0] for dec, number in zip(decimal_list, numbers): print("Round up {} to {} decimal: {}".format(number, dec, decimal_ceil(number, dec)))
Output:
Rounding Numpy Arrays
The Numpy library is one of the most commonly used libraries in data science and scientific computing. This library provides many functions that can be used to round float numbers. We are going to look at a few here.
np.round(a[, decimals=0, out=None])
It takes in an array-like input data and an optional number of decimal places to round to (default to 0). Just as we saw with the built-in round() above, this Numpy round() function uses the ROUND HALF TO EVEN strategy and can take in a negative value for the decimal argument.
It is not a built-in library, so needs to be installed. It comes installed with Anaconda, or we can run this command on our terminal to get it installed.
pip3 install numpy
Example 12: Round array of floating-point numbers
import numpy as np # seed so that the same output can be reproduced np.random.seed(234) def round_np(array_data, decimal=0): return np.round(array_data, decimal) if __name__ == '__main__': data = np.random.randn(3,3) print("Raw data:\n {}".format(data)) decimals = 3 result = round_np(data, decimals) print("Rounded data to {} decimals:\n {} ".format(decimals, result))
Output:
The cool thing about this numpy.round() function is that it takes in an array-like data. Unlike the built-in round() function, this function acts on a list or array of numbers at a time.
NB: In order to reproduce the same result above, make sure to seed with 234 as in the code above.
Other Functions
In this section, we shall take a look at a few other functions that Numpy provides for rounding floating-point numbers into the nearest integer;
Table 2: Numpy functions for rounding floating-point numbers to the nearest integer.
Numpy function | Description |
---|---|
numpy.floor() | Rounds every floating-point number in an array to the nearest integer lesser or equal to the original number. |
numpy.ceil() | Rounds every floating-point number in an array up to the nearest integer greater or equal to the original number |
numpy.truct() | Rounds every floating-point number in an array to its integer component. |
numpy.rint() | Rounds every floating-point number to its nearest integer using the ROUNDING HALF TO EVEN strategy |
Just as we saw with the built-in math library, we can also customize these functions so that they can round to a specified number of decimal places.
Example 13: Customize the numpy.floor() function to accept an argument for the number of decimal places to round to.
import numpy as np def rounding_down(array_data, decimal=0): tens = 10.0 if not isinstance(decimal, int): raise TypeError("Argument 'decimal' must be of type int") if decimal == 0: return np.floor(array_data) multiples = tens ** decimal return np.floor(array_data * multiples) / multiples if __name__ == '__main__': np.random.seed(234) data = np.random.randn(3,3) decimals = 3 print("Original Data: {}".format(data)) print("Round Down to {} decimals:\n {}".format(decimals, rounding_down(data, decimals)))
Output:
Rounding Pandas Series and DataFrame
The Pandas library is also one of the most commonly used libraries in data science and data analysis. Just as Numpy, It also comes installed with Anaconda or we can run the command below to install it.
pip3 install pandas
Pandas have the DataFrame and Series as their data structures and both have the .round() function that works exactly like np.round() we saw above.
Example 14: Rounding with Pandas
In this example, we shall see how to round Series and DataFrame objects, also, we shall see how to use numpy’s rounding functions on Pandas Series and DataFrame objects.
import pandas as pd import numpy as np np.random.seed(123) series = pd.Series(np.random.rand(3)) df = pd.DataFrame(np.random.randn(3,4), columns=['A','B','C','D']) print("Original Series:\n {}".format(series)) print("Original DF:\n {}".format(df)) print("\n") # Use .round() and round all data the same on Dataframe and Series decimals = 3 series_result = series.round(decimals) df_result = df.round(decimals) print("Series rounded to {} decimal:\n {}".format(decimals,series_result)) print("DF rounded to {} decimal:\n {}".format(decimals,df_result)) print("\n") # Use .round() and specify column-by-column precision on DataFrame decimals = {"A":2, "B":3, "C":4, "D":5} df_result = df.round(decimals) print("DF rounded to {} decimal:\n {}".format(decimals,df_result)) print("\n") # Use numpy's rounding functions on DataFrame print("DF rounded with np.floor:\n {}".format(np.floor(df))) print("DF rounded with np.ceil:\n {}".format(np.ceil(df)))
Output:
Frequently Asked Questions
Q #1) How do you round a number in a list Python?
Answer: We have many ways to round numbers in a list in Python. The first will be to use a list comprehension together with any rounding function like below:
>>> a =[1.234, 2.345, 3.45, 1.45] >>> [round(i,1) for i in a] # for each number, round with round() function [1.2, 2.3, 3.5, 1.4]
In this case, the most common way will be to use the numpy.round() method:
>>> import numpy as np >>> a = [1.234, 2.345, 3.45, 1.45] >>> np_a = np.array(a) # convert list to numpy array >>> np_a_round = np.round(np_a, 1) # round to 1 decimal place >>> list(np_a_round) # convert numpy array back to list. [1.2, 2.3, 3.4, 1.4]
Q #2) What is round() in Python
Answer: The round() is a built-in function in Python that takes in two arguments; the number to round(number) and the number of decimal places to keep(ndigits), then returns the closest multiple of 10-ndigits but breaks ties by applying the ROUNDING HALF TO EVEN strategy, which rounds to the nearest least even number.
Q #3) How do you round to 5 decimal places in Python?
Answer: With the built-in round() function, we can round to any decimal place by providing the number of decimal places to keep as the second argument. Say we want to round 3.45610688 to 5 decimal places.
>>> number = 3.45610688 >>> round(number, 5) 3.45611
Q #4) How do you round off without using the round function in Python?
Answer: To round up or down without the built-in round(), we first multiply the number to be rounded by 10.0n, where n is the decimal place to keep. Then, we use math.trunc() for truncation, math.ceil() for rounding up or math.floor() for rounding down. Finally, we divide the result by the same 10.0n.
Below is an example for rounding up:
multiples = math.pow(number, n) math.ceil(number * multiples) / multiples
Q #5) How do you round to the nearest whole number?
Answer: To round to the nearest whole number, we first shift the decimal point n times where n is the number of decimal places to round. Then we check the first digit after the decimal point.
If it is less than 5, we do nothing, but if it is greater or equal to 5, we round up the digit before the decimal point by adding 1 to it. Finally, we shift the decimal point back to its original position.
Conclusion
In this article, we looked at what rounding numbers are and they apply to floating-point numbers. We examined the Python built-in round() function and also looked at rounding with the math module where we saw various rounding strategies like Truncation, Rounding Up, Rounding Down, and more.
We also covered rounding with the Decimal module and finally, we looked at rounding Numpy arrays and Pandas Series and DataFrames.
=> Read Through ALL Python Tutorials Here