Rounding decimals and integers in Python with “round” and “Decimal.quantize

Money and Business

The following explains how to round numbers in Python by rounding or rounding to an even number. The numbers are assumed to be of floating point float or integer int type.

  • built-in function (e.g. in programming language): round()
    • Round decimals to any number of digits.
    • Round integers to any number of digits.
    • round() rounds to an even number, not to a common rounding
  • standard librarydecimal quantize()
    • DecimalCreating an object
    • Rounding of decimals to any number of digits and rounding to even numbers
    • Rounding of integers to any number of digits and rounding to even numbers
  • Define a new function
    • Round off decimals to any number of digits.
    • Round integers to any number of digits
    • Note: For negative values

Note that, as mentioned above, the built-in function round is not a general rounding, but a rounding to an even number. See below for details.

built-in function (e.g. in programming language): round()

Round() is provided as a built-in function. It can be used without importing any modules.

The first argument is the original number, and the second argument is the number of digits (how many digits to round to).

Round decimals to any number of digits.

The following is an example of processing for the floating-point float type.

If the second argument is omitted, it is rounded to an integer. The type also becomes an integer int type.

f = 123.456

print(round(f))
# 123

print(type(round(f)))
# <class 'int'>

If the second argument is specified, it returns a floating-point float type.

If a positive integer is specified, the decimal place is specified; if a negative integer is specified, the integer place is specified. -1 rounds to the nearest tenth, -2 rounds to the nearest hundredth, and 0 rounds to an integer (the first place), but returns a float type, unlike when omitted.

print(round(f, 1))
# 123.5

print(round(f, 2))
# 123.46

print(round(f, -1))
# 120.0

print(round(f, -2))
# 100.0

print(round(f, 0))
# 123.0

print(type(round(f, 0)))
# <class 'float'>

Round integers to any number of digits.

The following is an example of processing for integer int type.

If the second argument is omitted, or if 0 or a positive integer is specified, the original value is returned as is. If a negative integer is specified, it is rounded to the corresponding integer digit. In both cases, an integer int type is returned.

i = 99518

print(round(i))
# 99518

print(round(i, 2))
# 99518

print(round(i, -1))
# 99520

print(round(i, -2))
# 99500

print(round(i, -3))
# 100000

round() rounds to an even number, not to a common rounding

Note that rounding with the built-in round() function in Python 3 rounds to an even number, not to a general rounding.

As written in the official documentation, 0.5 is rounded to 0, 5 is rounded to 0, and so on.

print('0.4 =>', round(0.4))
print('0.5 =>', round(0.5))
print('0.6 =>', round(0.6))
# 0.4 => 0
# 0.5 => 0
# 0.6 => 1

print('4 =>', round(4, -1))
print('5 =>', round(5, -1))
print('6 =>', round(6, -1))
# 4 => 0
# 5 => 0
# 6 => 10

The definition of rounding to an even number is as follows.

If the fraction is less than 0.5, round it down; if the fraction is greater than 0.5, round it up; if the fraction is exactly 0.5, round it up to the even number between rounding down and rounding up.
Rounding – Wikipedia

0.5 is not always truncated.

print('0.5 =>', round(0.5))
print('1.5 =>', round(1.5))
print('2.5 =>', round(2.5))
print('3.5 =>', round(3.5))
print('4.5 =>', round(4.5))
# 0.5 => 0
# 1.5 => 2
# 2.5 => 2
# 3.5 => 4
# 4.5 => 4

In some cases, the definition of rounding to an even number does not even apply to processing after two decimal places.

print('0.05 =>', round(0.05, 1))
print('0.15 =>', round(0.15, 1))
print('0.25 =>', round(0.25, 1))
print('0.35 =>', round(0.35, 1))
print('0.45 =>', round(0.45, 1))
# 0.05 => 0.1
# 0.15 => 0.1
# 0.25 => 0.2
# 0.35 => 0.3
# 0.45 => 0.5

This is due to the fact that decimals cannot be represented exactly as floating point numbers, as stated in the official documentation.

The behavior of round() for floating point numbers may surprise you:For example, round(2.675, 2) will give you 2.67 instead of 2.68 as expected. This is not a bug.:This is a result of the fact that most decimals cannot be represented exactly by floating point numbers.
round() — Built-in Functions — Python 3.10.2 Documentation

If you want to achieve general rounding or accurate rounding of decimals to even numbers, you can use the standard library decimal quantize (described below) or define a new function.

Also note that round() in Python 2 is not rounding to an even number, but rounding.

quantize() of the standard library decimal

The decimal module of the standard library can be used to handle exact decimal floating point numbers.

Using the quantize() method of the decimal module, it is possible to round numbers by specifying the rounding mode.

The set values for the argument rounding of the quantize() method have the following meanings, respectively.

  • ROUND_HALF_UP:General rounding
  • ROUND_HALF_EVEN:Rounding to even numbers

The decimal module is a standard library, so no additional installation is required, but importing is necessary.

from decimal import Decimal, ROUND_HALF_UP, ROUND_HALF_EVEN

Creating a Decimal object

Decimal() can be used to create objects of type Decimal.

If you specify a float type as an argument, you can see what the value is actually treated as.

print(Decimal(0.05))
# 0.05000000000000000277555756156289135105907917022705078125

print(type(Decimal(0.05)))
# <class 'decimal.Decimal'>

As shown in the example, 0.05 is not treated as exactly 0.05. This is the reason why the built-in function round() described above rounded to a different value than expected for decimal values including 0.05 in the example.

Since 0.5 is one-half (-1 power of 2), it can be expressed exactly in binary notation.

print(Decimal(0.5))
# 0.5

If you specify the string type str instead of the float type, it will be treated as the Decimal type of the exact value.

print(Decimal('0.05'))
# 0.05

Rounding of decimals to any number of digits and rounding to even numbers

Call quantize() from an object of type Decimal to round off the value.

The first argument of quantize() is a string with the same number of digits as the number of digits you want to find, such as '0.1' or '0.01'.

In addition, the argument ROUNDING specifies the rounding mode; if ROUND_HALF_UP is specified, general rounding is used.

f = 123.456

print(Decimal(str(f)).quantize(Decimal('0'), rounding=ROUND_HALF_UP))
# 123

print(Decimal(str(f)).quantize(Decimal('0.1'), rounding=ROUND_HALF_UP))
# 123.5

print(Decimal(str(f)).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP))
# 123.46

Unlike the built-in function round(), 0.5 is rounded to 1.

print('0.4 =>', Decimal(str(0.4)).quantize(Decimal('0'), rounding=ROUND_HALF_UP))
print('0.5 =>', Decimal(str(0.5)).quantize(Decimal('0'), rounding=ROUND_HALF_UP))
print('0.6 =>', Decimal(str(0.6)).quantize(Decimal('0'), rounding=ROUND_HALF_UP))
# 0.4 => 0
# 0.5 => 1
# 0.6 => 1

If the argument rounding is set to ROUND_HALF_EVEN, rounding is performed to even numbers as in the built-in function round().

As mentioned above, if a floating-point float type is specified as the argument of Decimal(), it is treated as a Decimal object with a value equal to the actual value of the float type, so the result of using the quantize() method will be different from what is expected, just like the built-in function round().

print('0.05 =>', round(0.05, 1))
print('0.15 =>', round(0.15, 1))
print('0.25 =>', round(0.25, 1))
print('0.35 =>', round(0.35, 1))
print('0.45 =>', round(0.45, 1))
# 0.05 => 0.1
# 0.15 => 0.1
# 0.25 => 0.2
# 0.35 => 0.3
# 0.45 => 0.5

print('0.05 =>', Decimal(0.05).quantize(Decimal('0.1'), rounding=ROUND_HALF_EVEN))
print('0.15 =>', Decimal(0.15).quantize(Decimal('0.1'), rounding=ROUND_HALF_EVEN))
print('0.25 =>', Decimal(0.25).quantize(Decimal('0.1'), rounding=ROUND_HALF_EVEN))
print('0.35 =>', Decimal(0.35).quantize(Decimal('0.1'), rounding=ROUND_HALF_EVEN))
print('0.45 =>', Decimal(0.45).quantize(Decimal('0.1'), rounding=ROUND_HALF_EVEN))
# 0.05 => 0.1
# 0.15 => 0.1
# 0.25 => 0.2
# 0.35 => 0.3
# 0.45 => 0.5

If the argument of Decimal() is specified as a string of type str, it is treated as a Decimal object of exactly that value, so the result is as expected.

print('0.05 =>', Decimal(str(0.05)).quantize(Decimal('0.1'), rounding=ROUND_HALF_EVEN))
print('0.15 =>', Decimal(str(0.15)).quantize(Decimal('0.1'), rounding=ROUND_HALF_EVEN))
print('0.25 =>', Decimal(str(0.25)).quantize(Decimal('0.1'), rounding=ROUND_HALF_EVEN))
print('0.35 =>', Decimal(str(0.35)).quantize(Decimal('0.1'), rounding=ROUND_HALF_EVEN))
print('0.45 =>', Decimal(str(0.45)).quantize(Decimal('0.1'), rounding=ROUND_HALF_EVEN))
# 0.05 => 0.0
# 0.15 => 0.2
# 0.25 => 0.2
# 0.35 => 0.4
# 0.45 => 0.4

Since 0.5 can be correctly handled by the float type, there is no problem in specifying the float type as the argument of Decimal() when rounding to an integer, but it is safer to specify the string str type when rounding to a decimal place.

For example, 2.675 is actually 2.67499…. in float type. Therefore, if you want to round to two decimal places, you must specify a string to Decimal(), otherwise the result will be different from the expected result whether you round to the nearest whole number (ROUND_HALF_UP) or to an even number (ROUND_HALF_EVEN).

print(Decimal(2.675))
# 2.67499999999999982236431605997495353221893310546875

print(Decimal(2.675).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP))
# 2.67

print(Decimal(str(2.675)).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP))
# 2.68

print(Decimal(2.675).quantize(Decimal('0.01'), rounding=ROUND_HALF_EVEN))
# 2.67

print(Decimal(str(2.675)).quantize(Decimal('0.01'), rounding=ROUND_HALF_EVEN))
# 2.68

Note that the quantize() method returns a Decimal type number, so if you want to operate on a float type number, you need to convert it to a float type using float(), otherwise an error will occur.

d = Decimal('123.456').quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)

print(d)
# 123.46

print(type(d))
# <class 'decimal.Decimal'>

# print(1.2 + d)
# TypeError: unsupported operand type(s) for +: 'float' and 'decimal.Decimal'

print(1.2 + float(d))
# 124.66

Rounding of integers to any number of digits and rounding to even numbers

If you want to round to an integer digit, specifying something like '10' as the first argument will not give you the desired result.

i = 99518

print(Decimal(i).quantize(Decimal('10'), rounding=ROUND_HALF_UP))
# 99518

This is because quantize() performs rounding according to the exponent of the Decimal object, but the exponent of Decimal('10') is 0, not 1.

You can specify an arbitrary exponent by using E as an exponent string (e.g., '1E1'). The exponent exponent can be checked in the as_tuple method.

print(Decimal('10').as_tuple())
# DecimalTuple(sign=0, digits=(1, 0), exponent=0)

print(Decimal('1E1').as_tuple())
# DecimalTuple(sign=0, digits=(1,), exponent=1)

As it is, the result will be in exponential notation using E. If you want to use normal notation, or if you want to operate with integer int type after rounding, use int() to convert the result.

print(Decimal(i).quantize(Decimal('1E1'), rounding=ROUND_HALF_UP))
# 9.952E+4

print(int(Decimal(i).quantize(Decimal('1E1'), rounding=ROUND_HALF_UP)))
# 99520

print(int(Decimal(i).quantize(Decimal('1E2'), rounding=ROUND_HALF_UP)))
# 99500

print(int(Decimal(i).quantize(Decimal('1E3'), rounding=ROUND_HALF_UP)))
# 100000

If the argument rounding is set to ROUND_HALF_UP, general rounding will occur, e.g., 5 will be rounded to 10.

print('4 =>', int(Decimal(4).quantize(Decimal('1E1'), rounding=ROUND_HALF_UP)))
print('5 =>', int(Decimal(5).quantize(Decimal('1E1'), rounding=ROUND_HALF_UP)))
print('6 =>', int(Decimal(6).quantize(Decimal('1E1'), rounding=ROUND_HALF_UP)))
# 4 => 0
# 5 => 10
# 6 => 10

Of course, there is no problem if you specify it as a string.

Define a new function

The method of using the decimal module is accurate and secure, but if you are not comfortable with type conversion, you can define a new function to achieve general rounding.

There are many possible ways to do this, for example, the following function.

def my_round(val, digit=0):
    p = 10 ** digit
    return (val * p * 2 + 1) // 2 / p

If you don't need to specify the number of digits and always round to the first decimal place, you can use a simpler form.

my_round_int = lambda x: int((x * 2 + 1) // 2)

If you need to be precise, it is safer to use decimal.

The following is for reference only.

Round off decimals to any number of digits.

print(int(my_round(f)))
# 123

print(my_round_int(f))
# 123

print(my_round(f, 1))
# 123.5

print(my_round(f, 2))
# 123.46

Unlike round, 0.5 becomes 1 as per general rounding.

print(int(my_round(0.4)))
print(int(my_round(0.5)))
print(int(my_round(0.6)))
# 0
# 1
# 1

Round integers to any number of digits

i = 99518

print(int(my_round(i, -1)))
# 99520

print(int(my_round(i, -2)))
# 99500

print(int(my_round(i, -3)))
# 100000

Unlike round, 5 becomes 10 as per common rounding.

print(int(my_round(4, -1)))
print(int(my_round(5, -1)))
print(int(my_round(6, -1)))
# 0
# 10
# 10

Note: For negative values

In the example function above, -0.5 is rounded to 0.

print(int(my_round(-0.4)))
print(int(my_round(-0.5)))
print(int(my_round(-0.6)))
# 0
# 0
# -1

There are various ways of thinking about rounding for negative values, but if you want to make -0.5 into -1, you can modify it as follows, for example

import math

def my_round2(val, digit=0):
    p = 10 ** digit
    s = math.copysign(1, val)
    return (s * val * p * 2 + 1) // 2 / p * s

print(int(my_round2(-0.4)))
print(int(my_round2(-0.5)))
print(int(my_round2(-0.6)))
# 0
# -1
# -1