Python has a built-in function round()
that will, as the name suggests, round a number off:
unrounded = 5.99
rounded = round(unrounded)
print(rounded)
## 6
Note that, by default, this uses the round half to even rule for tie-breaking multiples of 0.5. In other words, any number that ends in “.5” gets rounded to the nearest EVEN whole number:
# 2.5 gets rounded DOWN
print(round(2.5))
## 2
# 3.5 gets rounded UP
print(round(3.5))
## 4
This is true even for negative numbers:
print(round(-2.5))
## -2
This method of rounding off is known as statistician’s rounding and avoids both a positive/negative bias and a bias towards/away from zero
The built-in math
library contains the floor()
function that will round numbers ‘towards the floor’ (ie towards negative infinity):
import math
negative_example = math.floor(-2.9)
positive_example = math.floor(2.9)
print(negative_example, positive_example, sep=', ')
## -3, 2
The complement to floor()
is ceil()
which will round numbers ‘towards the ceiling’ (ie towards positive infinity):
negative_example = math.ceil(-2.9)
positive_example = math.ceil(2.9)
print(negative_example, positive_example, sep=', ')
## -2, 3
The trunc()
function truncates a number, which means that it throws away the part after the decimal point. This is equivalent to rounding towards zero:
negative_example = math.trunc(-2.9)
positive_example = math.trunc(2.9)
print(negative_example, positive_example, sep=', ')
## -2, 2
There is no single function to do this in math
(not even in numpy
)! However, here’s a quick custom function you can use:
def round_away_from_zero(x):
"""Round a number x away from 0."""
return math.ceil(abs(x)) * math.copysign(1, x)
negative_example = round_away_from_zero(-2.9)
positive_example = round_away_from_zero(2.9)
print(negative_example, positive_example, sep=', ')
## -3.0, 3.0
The abs()
function takes the absolute value of the un-rounded number (ie makes it positive if it’s negative), rounds it up with ceil()
then multiplies that answer by either -1 or 1 using the copysign()
function to copy the sign of the original number.
The round()
function can take a second argument, namely the number of decimal places you want to round off to:
unrounded = 6.283185
rounded = round(unrounded, 3)
print(rounded)
## 6.283
Again, this uses statistician’s rounding:
# Round DOWN to 6.28318
print(round(6.283185, 5))
## 6.28318
Another method for rounding off is to use the .xf
notation with formatted strings (usually these will be ‘f-strings’ - see this page for more info - but you can also use this notation with the older .format()
method or %-sign notation). .xf
will round a floating-point number off to x places after the decimal point and it can be used with f-strings as follows:
unrounded = 6.283185
rounded = f'The number rounded to 3 decimal places is: {unrounded:.3f}'
print(rounded)
## The number rounded to 3 decimal places is: 6.283
Note that the result is a string not a number! So you can’t do any further calculation with it when using this method.
Instead of rounding off to a certain number of decimal places we often want to round off to a certain number of significant figures - digits excluding leading zeroes. A useful library that will do this is sigfig
which can be installed from the terminal with:
# If you have a version of Python that isn't 3.9 use those version numbers instead
python3.9 -m pip install sigfig
This includes a new implementation of the round()
function that overwrites the built-in one. It can round off to a given number of decimal places using the decimals
keyword argument like so:
import sigfig
unrounded = 1.2345
rounded = sigfig.round(unrounded, decimals=3)
print(f'{1.2345} becomes {rounded}')
## 1.2345 becomes 1.235
Note that this function rounds half away from zero, also known as commercial rounding: 1.2345 gets rounded to 1.235 as opposed to statistician’s rounding which would make it 1.234
This implementation of round()
can also round off to a given number of significant figures via the sigfigs
keyword argument:
unrounded = 1.2345
rounded = sigfig.round(unrounded, sigfigs=3)
print(f'{1.2345} becomes {rounded}')
## 1.2345 becomes 1.23
Note that exponential notation is preserved unless you convert to a string before invoking the function:
unrounded = 1.2345e-7
exponential = sigfig.round(unrounded, sigfigs=3)
floating = sigfig.round(str(unrounded), sigfigs=3)
print(f'{1.2345e-7} becomes {exponential} or {floating}')
## 1.2345e-07 becomes 1.23e-07 or 0.000000123
This function can also incorporate uncertainty using the uncertainty
keyword:
unrounded = 1.2345
rounded = sigfig.round(unrounded, uncertainty=0.0002)
print(f'{1.2345} becomes {rounded}')
## 1.2345 becomes 1.2345 ± 0.0002
And, finally, it can use different representations of uncertainty:
# Estimate Newton's gravitational constant
print('G =', sigfig.round('6.67430', '0.00015', format='Drake'), '×10^−11 m3⋅kg−1⋅s−2')
## G = 6.674 30(15) ×10^−11 m3⋅kg−1⋅s−2
If you are absolutely set on using statistician’s rounding (or if you want to make any other modifications to how the sigfig
package does its rounding), or if you only want to use built-in packages instead of relying on external dependencies, you can re-create the function yourself. This has been done below:
from decimal import Decimal, getcontext
def round_to_sig_figs(x, sigfigs):
"""Round a number off to a given number of significant figures."""
# A special case is if the number is 0
if float(x) == 0.0:
output = '0' * sigfigs
# Insert a decimal point
output = output[0] + '.' + output[1:]
return output
# A special case is if the number is 'nan'
if np.isnan(x):
return 'None'
# Set the precision
getcontext().prec = sigfigs
rounded = Decimal(x)
# The precision only kicks in when an arithmetic operation is performed
rounded = rounded * 1
# Remove scientific notation
# (if the order of magnitude of the number is larger than the number of
# significant figures, the number will have been converted to scientific
# notation)
rounded = float(rounded)
# Convert to string
output = str(rounded)
# Count the number of significant figures
sigfigs_now = len(output.replace('-', '').replace('.', '').lstrip('0'))
# Remove trailing zero if one exists and it is not necessary
if ((sigfigs_now > sigfigs) and (output.endswith('.0'))):
output = output.removesuffix('.0')
# Add trailing zeroes if necessary
if sigfigs_now < sigfigs:
discrepancy = sigfigs - sigfigs_now
# Append a decimal point if necessary
if '.' not in output:
output = output + '.'
# Add trailing zeroes
output = output + '0' * discrepancy
return output
Let’s test it:
unrounded = 1.2345
rounded = round_to_sig_figs(unrounded, 4)
print(f'{1.2345} becomes {rounded}')
## 1.2345 becomes 1.234
Another useful library to have is engineering_notation
. It can be installed from the terminal with:
# If you have a version of Python that isn't 3.9 use those version numbers instead
python3.9 -m pip install engineering_notation
This library contains the EngNumber()
function which converts a number into ‘engineering notation’ (ie it represents it as a multiple of \(10^x\) or of \(1/10^x\) where \(x\) is a multiple of 3) using the usual SI units. It then rounds off to a given precision (number of decimal place; 2 by default) or to a given level of significance:
from engineering_notation import EngNumber
# Represent a large number in engineering notation with a unit
# Use default rounding (to 2 decimal places)
raw = 12345
formatted = EngNumber(raw)
print(f'{raw} becomes {formatted}')
## 12345 becomes 12.34k
# Represent a small number in engineering notation with a unit
# Round to 1 decimal place
raw = 0.012345
formatted = EngNumber(raw, precision=1)
print(f'{raw} becomes {formatted}')
## 0.012345 becomes 12.3m
# Represent a number in engineering notation
# Round to 4 significant figures
raw = 6.283185
formatted = EngNumber(raw, significant=4)
print(f'{raw} becomes {formatted}')
## 6.283185 becomes 6.283
If you want to tweak the output of EngNumber()
you can copy the function’s source code from GitHub and edit it yourself. Here’s an example of exactly that: the EngNumber()
function re-moulded into a new function round_engineering()
that represents the output in a variant of exponential notation:
from decimal import Decimal
def round_engineering(value, precision=2, significant=0):
"""
Round and convert to an exponential format that is a multiple of 3.
:return: A string representing the engineering number
"""
number = Decimal(str(value))
# Since the Decimal class only really converts numbers that are very small
# into engineering notation we will simply make all numbers small numbers.
# We can then take advantage of the Decimal class
num_str = number * Decimal('10e-25')
num_str = num_str.to_eng_string().lower()
base, exponent = num_str.split('e')
if significant > 0:
if abs(Decimal(base)) >= 100.0:
base = str(round(Decimal(base), significant - 3))
elif abs(Decimal(base)) >= 10.0:
base = str(round(Decimal(base), significant - 2))
else:
base = str(round(Decimal(base), significant - 1))
else:
base = str(round(Decimal(base), precision))
if 'e' in base.lower():
base = str(int(Decimal(base)))
# Remove trailing decimals
# https://stackoverflow.com/questions/3410976/how-to-round-a-number-to-significant-figures-in-python
# https://stackoverflow.com/questions/11227620/drop-trailing-zeros-from-decimal
if '.' in base:
base = base.rstrip('.')
# Remove trailing .00 in precision 2
if precision == 2 and significant == 0:
if '.00' in base:
base = base[:-3]
# Scale the exponent back to normal
exponent = str(int(exponent) + 24)
# Format the exponent
if exponent == '0':
exponent_str = ''
else:
exponent_str = '×10^' + exponent
return base + exponent_str
Here’s how it’s used:
numbers = [
1,
12,
123,
1234,
12345,
123456,
0.1234,
1.234,
12.34,
123.4,
1234,
]
for number in numbers:
print(f'{number:>7} becomes {round_engineering(number, significant=3)}')
## 1 becomes 1.00
## 12 becomes 12.0
## 123 becomes 123
## 1234 becomes 1.23×10^3
## 12345 becomes 12.3×10^3
## 123456 becomes 123×10^3
## 0.1234 becomes 123×10^-3
## 1.234 becomes 1.23
## 12.34 becomes 12.3
## 123.4 becomes 123
## 1234 becomes 1.23×10^3