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.
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.
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.
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.
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.
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.
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.
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.
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:
ROUND_CEILING- round towards positive infinityROUND_DOWN- round towards zeroROUND_FLOOR- round towards negative infinityROUND_HALF_DOWN- round to nearest, with ties going towards zeroROUND_HALF_EVEN- round to nearest, with ties going to the nearest even digitROUND_HALF_UP- round to nearest, with ties going away from zeroROUND_UP- round away from zeroROUND_05UP- round away from zero if the last digit after rounding towards zero would be 0 or 5; otherwise round towards zero
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.
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.
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.
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.
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.
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
List all Python tutorials.