ZetCode

Python Decimal

last modified January 29, 2024

In this article we show how to perform high-precision calculations in Python with Decimal.

Python decimal

The Python 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 double precision floating point number. The Decimal is a floating decimal point type which has more precision and a smaller range than the float. It is appropriate for financial and monetary calculations. It is also closer to the way how humans work with numbers.

Unlike hardware based binary floating point, the decimal module has a user alterable precision which can be as large as needed for a given problem. The default precision is 28 places.

Some values cannot be exactly represented in a float data type. For instance, storing the 0.1 value in float (which is a binary floating point value) variable we get only an approximation of the value. Similarly, the 1/3 value cannot be represented exactly in decimal floating point type.

Neither of the types is perfect; generally, decimal types are better suited for financial and monetary calculations, while the double/float types for scientific calculations.

Python Decimal default precision

The Decimal has a default precision of 28 places, while the float has 18 places.

defprec.py
#!/usr/bin/python

from decimal import Decimal

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

print("-----------------------")

y = Decimal(1) / Decimal(3)
print(type(y))
print(y)

The example compares the precision of two floating point types in Python.

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

Python compare floating point 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
#!/usr/bin/python

from decimal import Decimal

x = 0.1 + 0.1 + 0.1

print(x == 0.3)
print(x)

print("----------------------")

x = Decimal('0.1') + Decimal('0.1') + Decimal('0.1')

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

The example performs a comparison of floating point values with the built-in float and the Decimal types.

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

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

Python Decimal altering precision

It is possible to change the default precision of the Decimal type. In the following example, we also use the mpmath module, which is a library for arbitrary-precision floating-point arithmetic.

$ pip install mpmath

We need to install mpmath first.

alter_precision.py
#!/usr/bin/python

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("   math.sqrt: {0}".format(Decimal(math.sqrt(num))))
print("decimal.sqrt: {0}".format(num.sqrt()))
print(" mpmath.sqrt: {0}".format(mpmath.sqrt(num2)))
print('actual value: 0.3779644730092272272145165362341800608157513118689214')

In the example, we change the precision to 50 places. We compare the accuracy of the math.sqrt, Decimal's sqrt, and mpmath.sqrt functions.

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

Python Decimal rounding

The Decimal type provides several rounding options:

rounding.py
#!/usr/bin/python

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

print(f"{' ':20} {'1/7 (1)':^10} {'1/7 (2)':^10} {'1/7 (3)':^10} {'1/7 (4)':^10}")
print(f"{' ':20} {col_lines:^10} {col_lines:^10} {col_lines:^10} {col_lines:^10}")

for mode in rounding_modes:

    print(f'{mode:20}', end=' ')

    for precision in [1, 2, 3, 4]:

        context.prec = precision
        context.rounding = getattr(decimal, mode)
        value = decimal.Decimal(1) / decimal.Decimal(7)
        print(f'{value:<10}', end=' ')
    print()

print('********************************************************************')

print(f"{' ':20} {'-1/7 (1)':^10} {'-1/7 (2)':^10} {'-1/7 (3)':^10} {'-1/7 (4)':^10}")
print(f"{' ':20} {col_lines:^10} {col_lines:^10} {col_lines:^10} {col_lines:^10}")


for mode in rounding_modes:

    print(f'{mode:20}', end=' ')

    for precision in [1, 2, 3, 4]:

        context.prec = precision
        context.rounding = getattr(decimal, mode)
        value = decimal.Decimal(-1) / decimal.Decimal(7)
        print(f'{value:<10}', end=' ')

    print()

The example presents the available rounding options for the 1/7 and -1/7 expressions.

$ ./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

Python Fraction

We can work with rational numbers using the Fraction.

fract.py
#!/usr/bin/python

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)

The Decimal cannot represent the 1/3 expression precisely. In some cases, we can use the Fraction type to get an accurate result.

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

Python SymPy Rational

It is also possible to use the SymPy's Rational type to work with rationals.

$ pip install sympy

We install the sympy module.

symbolic.py
#!/usr/bin/python

from sympy import Rational

r1 = Rational(1/10)
r2 = Rational(1/10)
r3 = Rational(1/10)

val = (r1 + r2 + r3) * 3
print(val.evalf())

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

In the example, we compare the precision of the Rational and the built-in float for the (1/10 + 1/10 + 1/10)*3 expression.

$ ./symbolic.py
0.900000000000000
0.9000000000000001

There is a small error for the float type.

Source

Decimal fixed point and floating point arithmetic

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

Author

My name is Jan Bodnar and I am a passionate programmer with many years of programming experience. I have been writing programming articles since 2007. So far, I have written over 1400 articles and 8 e-books. I have over eight years of experience in teaching programming.

List all Python tutorials.