ZetCode

Python Decimal

last modified October 30, 2026 Python 3.15

In this article we show how to perform exact decimal and high-precision calculations in Python with the Decimal type.

The decimal module

The decimal module provides support for fast, correctly rounded decimal floating point arithmetic.

By default, Python interprets any number that includes a decimal point as a binary double precision floating point number, the float type. A float has about 15 to 17 significant decimal digits. Decimal is a decimal floating point type with a user-alterable precision, which can be as large as needed for a given problem. The default precision is 28 significant digits.

Some values cannot be represented exactly in the binary float type. For instance, the 0.1 value stored in a float is only an approximation. Decimal stores 0.1 exactly, because it works in base ten, the way humans do. On the other hand, the 1/3 value cannot be represented exactly in either type.

Neither of the types is perfect; generally, decimal types are better suited for financial and monetary calculations, while floats are used for scientific calculations, where speed matters and small relative errors are acceptable.

Two examples use the third-party mpmath and sympy libraries.

$ pip install mpmath sympy

Default precision

The following example compares the precision of the two types.

defprec.py
from decimal import Decimal, getcontext

x = 1 / 3
print(type(x))
print(x)

print('-----------------------')

y = Decimal(1) / Decimal(3)
print(type(y))
print(y)
print(getcontext().prec)

We divide one by three with both types. The getcontext function returns the current arithmetic context, whose prec attribute holds the precision.

$ python defprec.py
<class 'float'>
0.3333333333333333
-----------------------
<class 'decimal.Decimal'>
0.3333333333333333333333333333
28

Creating Decimals

A Decimal can be created from an integer, a string, a float, or a tuple.

creating.py
from decimal import Decimal

print(Decimal('0.1'))
print(Decimal(0.1))
print(Decimal(42))
print(Decimal('-1.50'))
print(Decimal('1e-3'))
print(Decimal((0, (3, 1, 4), -2)))
print(Decimal('Infinity'), Decimal('NaN'))
print(Decimal(' 2.5\n'))

A Decimal created from a string is exact. A float is converted exactly as well, but the float value is already an approximation, so Decimal(0.1) reveals the binary error. That is why Decimals should be created from strings. Decimals keep trailing zeros; the -1.50 value remembers that it has two decimal places.

print(Decimal((0, (3, 1, 4), -2)))

The tuple contains the sign (0 for positive), the digits, and the exponent. Decimals also support special values such as infinity and NaN, and leading and trailing whitespace in strings is ignored.

$ python creating.py
0.1
0.1000000000000000055511151231257827021181583404541015625
42
-1.50
0.001
3.14
Infinity NaN
2.5

Comparing values

Caution should be exercised when comparing floating point values. While in many real world problems a small error is negligible, financial and monetary calculations must be exact.

comparing.py
from decimal import Decimal

x = 0.1 + 0.1 + 0.1

print(x == 0.3)
print(x)

print('----------------------')

y = Decimal('0.1') + Decimal('0.1') + Decimal('0.1')

print(y == Decimal('0.3'))
print(float(y) == 0.3)
print(y)

The example compares the sum of three 0.1 values with 0.3, first with the built-in float, then with the Decimal type.

Due to a small error in the float type, the 0.1 + 0.1 + 0.1 == 0.3 expression yields False. With the Decimal type, we get the expected result.

$ python comparing.py
False
0.30000000000000004
----------------------
True
True
0.3

Money calculations

In the next example, we compute an invoice total with value-added tax.

invoice.py
from decimal import Decimal, ROUND_HALF_UP

prices = ['19.99', '5.49', '0.10', '0.20', '12.35']
vat_rate = Decimal('0.23')

total = sum(Decimal(price) for price in prices)
vat = (total * vat_rate).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)

print(f'subtotal: {total}')
print(f'VAT 23%:  {vat}')
print(f'total:    {total + vat}')

ftotal = sum(float(price) for price in prices)
print(f'float subtotal: {ftotal!r}')

We sum the prices, which are given as strings, and compute a 23% tax.

vat = (total * vat_rate).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)

The quantize method rounds a value to the exponent of its argument; Decimal('0.01') means two decimal places. The tax is 8.7699, which rounds to 8.77. Summing the same prices as floats produces a value that is slightly off.

$ python invoice.py
subtotal: 38.13
VAT 23%:  8.77
total:    46.90
float subtotal: 38.129999999999995

Rounding money

The built-in round function works on floats, so its results can be surprising.

rounding_money.py
from decimal import Decimal, ROUND_HALF_UP, ROUND_HALF_EVEN

print(round(2.675, 2))
print(Decimal(2.675))

cents = Decimal('0.01')

for value in ['2.675', '2.665', '0.125', '-0.125']:
    d = Decimal(value)
    up = d.quantize(cents, rounding=ROUND_HALF_UP)
    even = d.quantize(cents, rounding=ROUND_HALF_EVEN)
    print(f'{value:>7} {up:>6} {even:>6}')

The round(2.675, 2) call returns 2.67, not 2.68, because the float closest to 2.675 is slightly smaller than 2.675. With Decimal, the value is exact and the rounding mode decides the result.

ROUND_HALF_UP rounds halves away from zero, the way taught at school. ROUND_HALF_EVEN, also called banker's rounding, rounds halves to the nearest even digit; it is the default mode of the module and avoids a bias when many values are rounded.

$ python rounding_money.py
2.67
2.67499999999999982236431605997495353221893310546875
  2.675   2.68   2.68
  2.665   2.67   2.66
  0.125   0.13   0.12
 -0.125  -0.13  -0.12

Precision and significant digits

The precision is the number of significant digits, not the number of decimal places. It applies to the results of arithmetic operations, not to the creation of Decimals.

significant.py
from decimal import Decimal, getcontext

getcontext().prec = 6

pi = Decimal('3.14159265358979323846')
print(pi)
print(+pi)
print(pi * 1)
print(Decimal('123456789') + 0)
print(Decimal('1.30') + Decimal('1.20'))
print((Decimal('1.30') + Decimal('1.20')).normalize())

We set the precision to six digits. The pi value keeps all its digits, because creating a Decimal is exact. The unary plus and the multiplication are arithmetic operations, so their results are rounded to six digits. A large integer is rounded too and printed in scientific notation.

print(Decimal('1.30') + Decimal('1.20'))
print((Decimal('1.30') + Decimal('1.20')).normalize())

Addition keeps the trailing zeros of the operands; the normalize method strips them.

$ python significant.py
3.14159265358979323846
3.14159
3.14159
1.23457E+8
2.50
2.5

Altering precision

In the following example, we raise the precision to 50 digits and compare the results with the mpmath module, a library for arbitrary-precision floating-point arithmetic.

alter_precision.py
from decimal import Decimal, getcontext
import math

import mpmath

getcontext().prec = 50
mpmath.mp.dps = 50

num = Decimal(1) / Decimal(7)
num2 = mpmath.mpf(1) / mpmath.mpf(7)

print(f'   math.sqrt: {Decimal(math.sqrt(num))}')
print(f'decimal.sqrt: {num.sqrt()}')
print(f' mpmath.sqrt: {mpmath.sqrt(num2)}')
print('actual value: 0.3779644730092272272145165362341800608157513118689214')

We compare the accuracy of the math.sqrt function, the sqrt method of Decimal, and the mpmath.sqrt function. The math.sqrt function converts its argument to a float; its result is correct only to about 16 digits. The digits after that are the exact decimal expansion of the float. The other two results agree with the actual value to 50 digits.

$ python alter_precision.py
   math.sqrt: 0.37796447300922719758631274089566431939601898193359375
decimal.sqrt: 0.37796447300922722721451653623418006081575131186892
 mpmath.sqrt: 0.37796447300922722721451653623418006081575131186892
actual value: 0.3779644730092272272145165362341800608157513118689214

Local contexts

Changing the global context affects all code in the thread. The localcontext function creates a temporary context for a block of code.

local_context.py
from decimal import Decimal, localcontext, getcontext

third = Decimal(1) / Decimal(3)
print(third)

with localcontext() as ctx:
    ctx.prec = 5
    print(Decimal(1) / Decimal(3))

with localcontext(prec=60):
    print(Decimal(1) / Decimal(3))

print(getcontext().prec)

Inside the with blocks, the division uses 5 and 60 digits of precision. Context attributes can be set on the returned object or passed as keyword arguments. After the blocks, the original precision of 28 digits is restored.

$ python local_context.py
0.3333333333333333333333333333
0.33333
0.333333333333333333333333333333333333333333333333333333333333
28

Rounding modes

The decimal module provides several rounding modes:

rounding.py
import decimal

context = decimal.getcontext()

rounding_modes = [
    'ROUND_CEILING',
    'ROUND_DOWN',
    'ROUND_FLOOR',
    'ROUND_HALF_DOWN',
    'ROUND_HALF_EVEN',
    'ROUND_HALF_UP',
    'ROUND_UP',
    'ROUND_05UP',
]

col_lines = '-' * 10


def show(numerator):
    labels = [f'{numerator}/7 ({precision})' for precision in range(1, 5)]
    print(' ' * 16, *(f'{label:^10}' for label in labels))
    print(' ' * 16, *[col_lines] * 4)

    for mode in rounding_modes:
        print(f'{mode:16}', end=' ')

        for precision in [1, 2, 3, 4]:
            context.prec = precision
            context.rounding = getattr(decimal, mode)
            value = decimal.Decimal(numerator) / decimal.Decimal(7)
            print(f'{value!s:<10}', end=' ')

        print()


show(1)
print()
show(-1)

The example applies all rounding modes to the 1/7 and -1/7 expressions with a precision of one to four digits. The context's rounding attribute sets the mode for arithmetic operations.

$ python rounding.py
                  1/7 (1)    1/7 (2)    1/7 (3)    1/7 (4)  
                 ---------- ---------- ---------- ----------
ROUND_CEILING    0.2        0.15       0.143      0.1429     
ROUND_DOWN       0.1        0.14       0.142      0.1428     
ROUND_FLOOR      0.1        0.14       0.142      0.1428     
ROUND_HALF_DOWN  0.1        0.14       0.143      0.1429     
ROUND_HALF_EVEN  0.1        0.14       0.143      0.1429     
ROUND_HALF_UP    0.1        0.14       0.143      0.1429     
ROUND_UP         0.2        0.15       0.143      0.1429     
ROUND_05UP       0.1        0.14       0.142      0.1428     

                  -1/7 (1)   -1/7 (2)   -1/7 (3)   -1/7 (4) 
                 ---------- ---------- ---------- ----------
ROUND_CEILING    -0.1       -0.14      -0.142     -0.1428    
ROUND_DOWN       -0.1       -0.14      -0.142     -0.1428    
ROUND_FLOOR      -0.2       -0.15      -0.143     -0.1429    
ROUND_HALF_DOWN  -0.1       -0.14      -0.143     -0.1429    
ROUND_HALF_EVEN  -0.1       -0.14      -0.143     -0.1429    
ROUND_HALF_UP    -0.1       -0.14      -0.143     -0.1429    
ROUND_UP         -0.2       -0.15      -0.143     -0.1429    
ROUND_05UP       -0.1       -0.14      -0.142     -0.1428    

Rounding halves

The modes differ most when a value lies exactly halfway between two results.

half_modes.py
from decimal import (Decimal, ROUND_HALF_UP, ROUND_HALF_DOWN, ROUND_HALF_EVEN,
                     ROUND_05UP)

modes = [('HALF_UP', ROUND_HALF_UP), ('HALF_DOWN', ROUND_HALF_DOWN),
         ('HALF_EVEN', ROUND_HALF_EVEN), ('05UP', ROUND_05UP)]

print(f"{'value':>6}", *(f'{name:>9}' for name, _ in modes))

for value in ['1.5', '2.5', '-2.5', '2.51', '5.4', '0.4']:
    d = Decimal(value)
    rounded = [d.quantize(Decimal('1'), rounding=mode) for _, mode in modes]
    print(f'{value:>6}', *(f'{r!s:>9}' for r in rounded))

We round the values to integers with quantize. The three half modes agree on 2.51, which is not a tie. ROUND_05UP is unusual: it truncates 5.4 to 5 and, since the last digit is 5, rounds away from zero to 6.

$ python half_modes.py
 value   HALF_UP HALF_DOWN HALF_EVEN      05UP
   1.5         2         1         2         1
   2.5         3         2         2         2
  -2.5        -3        -2        -2        -2
  2.51         3         3         3         2
   5.4         5         5         5         6
   0.4         0         0         0         1

Signals and traps

Exceptional conditions, such as division by zero, are signals. If the trap for a signal is enabled in the context, an exception is raised; otherwise a special value is returned.

signals.py
from decimal import (Decimal, DivisionByZero, InvalidOperation,
                     FloatOperation, localcontext)

try:
    Decimal(1) / Decimal(0)
except DivisionByZero as e:
    print(f'DivisionByZero: {e!r}')

try:
    Decimal('1,5')
except InvalidOperation as e:
    print(f'InvalidOperation: {e!r}')

try:
    Decimal('1.5') + 1.5
except TypeError as e:
    print(f'TypeError: {e}')

print(Decimal('1.5') < 2.0)

with localcontext() as ctx:
    ctx.traps[FloatOperation] = True
    try:
        Decimal('1.5') < 2.0
    except FloatOperation as e:
        print(f'FloatOperation: {e!r}')

with localcontext() as ctx:
    ctx.traps[DivisionByZero] = False
    print(Decimal(1) / Decimal(0))

Division by zero and invalid strings raise exceptions by default. Decimals cannot be added to floats, but they can be compared with them.

ctx.traps[FloatOperation] = True

Enabling the FloatOperation trap makes mixed comparisons raise an exception, which helps find floats that slipped into decimal code. With the DivisionByZero trap disabled, the division returns infinity.

$ python signals.py
DivisionByZero: DivisionByZero([<class 'decimal.DivisionByZero'>])
InvalidOperation: InvalidOperation([<class 'decimal.ConversionSyntax'>])
TypeError: unsupported operand type(s) for +: 'decimal.Decimal' and 'float'
True
FloatOperation: FloatOperation([<class 'decimal.FloatOperation'>])
Infinity

Decimal methods

The Decimal type has mathematical and inspection methods.

methods.py
from decimal import Decimal

d = Decimal('2')

print(d.sqrt())
print(d.exp())
print(d.ln())
print(Decimal('1000').log10())

price = Decimal('19.990')
print(price.as_tuple())
print(price.as_integer_ratio())
print(price.normalize())
print(price.is_signed(), (-price).is_signed())
print(int(price), float(price))

The sqrt, exp, ln, and log10 methods are computed to the context precision. The as_tuple method returns the sign, digits, and exponent, and as_integer_ratio returns the value as a fraction in lowest terms. Converting to int truncates the value.

$ python methods.py
1.414213562373095048801688724
7.389056098930650227230427461
0.6931471805599453094172321215
3
DecimalTuple(sign=0, digits=(1, 9, 9, 9, 0), exponent=-3)
(1999, 100)
19.99
False True
19 19.99

Fraction

Rational numbers can be represented exactly with the Fraction type of the fractions module.

fract.py
from decimal import Decimal
from fractions import Fraction

x = Decimal(1) / Decimal(3)
y = x * Decimal(3)

print(y == Decimal(1))
print(x)
print(y)

print('-----------------------')

u = Fraction(1) / Fraction(3)
v = u * Fraction(3)

print(v == 1)
print(u)
print(v)
print(Fraction('0.1') + Fraction(1, 5))

The Decimal type cannot represent the 1/3 expression precisely, so multiplying it by three does not give one. The Fraction type stores the numerator and the denominator and gives an exact result. A fraction can also be created from a decimal string.

$ python fract.py
False
0.3333333333333333333333333333
0.9999999999999999999999999999
-----------------------
True
1/3
1
3/10

SymPy Rational

It is also possible to use the Rational type of the SymPy library to work with rational numbers.

symbolic.py
from sympy import Rational

r = Rational(1, 10)
val = (r + r + r) * 3
print(val)
print(val.evalf())

print(Rational(1/10))

val2 = (1/10 + 1/10 + 1/10) * 3
print(val2)

We compare the Rational type and the built-in float for the (1/10 + 1/10 + 1/10) * 3 expression. The evalf method evaluates the exact result numerically.

print(Rational(1/10))

We must pass the numerator and the denominator as two integers. The 1/10 expression is evaluated as a float first, and its binary approximation becomes an unexpected fraction.

$ python symbolic.py
9/10
0.900000000000000
3602879701896397/36028797018963968
0.9000000000000001

Source

decimal - Decimal fixed-point and floating-point arithmetic

In this article we have worked with the Python Decimal type.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Python tutorials.