PyMOTW: decimal - Fixed and floating point math

By Doug Hellmann
August 30, 2009

decimal – Fixed and floating point math

Purpose:Decimal arithmetic using fixed and floating point numbers
Python Version:2.4 and later

The decimal module implements fixed and floating point arithmetic using the model familiar to most people, rather than the IEEE floating point version implemented by most computer hardware. A Decimal instance can represent any number exactly, round up or down, and apply a limit to the number of significant digits.

Decimal

Decimal values are represented as instances of the Decimal class. The constructor takes as argument an integer, or a string. Floating point numbers must be converted to a string before being used to create a Decimal, letting the caller explicitly deal with the number of digits for values that cannot be expressed exactly using hardware floating point representations.

import decimal

fmt = '{0:<20} {1:<20}'
print fmt.format('Input', 'Output')
print fmt.format('-' * 20, '-' * 20)

# Integer
print fmt.format(5, decimal.Decimal(5))

# String
print fmt.format('3.14', decimal.Decimal('3.14'))

# Float
print fmt.format(repr(0.1), decimal.Decimal(str(0.1)))

Notice that the floating point value of 0.1 is not represented as an exact value, so the representation as a float is different from the Decimal value.

$ python decimal_create.py
Input                Output
-------------------- --------------------
5                    5
3.14                 3.14
0.10000000000000001  0.1

Less conveniently, Decimals can also be created from tuples containing a sign flag (0 for positive, 1 for negative), a tuple of digits, and an integer exponent.

import decimal

# Tuple
t = (1, (1, 1), -2)
print 'Input  :', t
print 'Decimal:', decimal.Decimal(t)
$ python decimal_tuple.py
Input  : (1, (1, 1), -2)
Decimal: -0.11

Arithmetic

Decimal overloads the simple arithmetic operators so once you have a value you can manipulate it in much the same way as the built-in numeric types.

import decimal

a = decimal.Decimal('5.1')
b = decimal.Decimal('3.14')
c = 4
d = 3.14

print 'a     =', a
print 'b     =', b
print 'c     =', c
print 'd     =', d
print

print 'a + b =', a + b
print 'a - b =', a - b
print 'a * b =', a * b
print 'a / b =', a / b
print

print 'a + c =', a + c
print 'a - c =', a - c
print 'a * c =', a * c
print 'a / c =', a / c
print

print 'a + d =',
try:
    print a + d
except TypeError, e:
    print e

Decimal operators also accept integer arguments, but floating point values must be converted to Decimal instances.

$ python decimal_operators.py
a     = 5.1
b     = 3.14
c     = 4
d     = 3.14

a + b = 8.24
a - b = 1.96
a * b = 16.014
a / b = 1.624203821656050955414012739

a + c = 9.1
a - c = 1.1
a * c = 20.4
a / c = 1.275

a + d = unsupported operand type(s) for +: 'Decimal' and 'float'

Logarithms

Beyond basic arithmetic, Decimal includes methods to find the base 10 and natural logarithms.

import decimal

d = decimal.Decimal(100)
print 'd     :', d
print 'log10 :', d.log10()
print 'ln    :', d.ln()
$ python decimal_log.py
d     : 100
log10 : 2
ln    : 4.605170185988091368035982909

Special Values

In addition to the expected numerical values, Decimal can represent several special values, including positive and negative values for infinity, “not a number”, and zero.

import decimal

for value in [ 'Infinity', 'NaN', '0' ]:
    print decimal.Decimal(value), decimal.Decimal('-' + value)
print

# Math with infinity
print 'Infinity + 1:', (decimal.Decimal('Infinity') + 1)
print '-Infinity + 1:', (decimal.Decimal('-Infinity') + 1)

# Print comparing NaN
print decimal.Decimal('NaN') == decimal.Decimal('Infinity')
print decimal.Decimal('NaN') != decimal.Decimal(1)

Adding to infinite values returns another infinite value. Comparing for equality with NaN always returns False and comparing for inequality always returns true. Comparing for sort order against NaN is undefined and results in an error.

$ python decimal_special.py
Infinity -Infinity
NaN -NaN
0 -0

Infinity + 1: Infinity
-Infinity + 1: -Infinity
False
True

Context

So far all of the examples have used the default behaviors of the decimal module. It is possible to override settings such as the precision maintained, how rounding is performed, error handling, etc. All of these settings are maintained via a context. Contexts can be applied for all Decimal instances in a thread or locally within a small code region.

Current Context

To retrieve the current global context, use getcontext().

import decimal

print decimal.getcontext()
$ python decimal_getcontext.py
Context(prec=28, rounding=ROUND_HALF_EVEN, Emin=-999999999, Emax=999999999, capitals=1, flags=[], traps=[Overflow, InvalidOperation, DivisionByZero])

Precision

The prec attribute of the context controls the precision maintained for new values created as a result of arithmetic. Literal values are maintained as described.

import decimal

d = decimal.Decimal('0.123456')

for i in range(4):
    decimal.getcontext().prec = i
    print i, ':', d, d * 1
$ python decimal_precision.py
0 : 0.123456 0
1 : 0.123456 0.1
2 : 0.123456 0.12
3 : 0.123456 0.123

Rounding

There are several options for rounding to keep values within the desired precision.

ROUND_CEILING
Always round upwards towards infinity.
ROUND_DOWN
Always round toward zero.
ROUND_FLOOR
Always round down towards negative infinity.
ROUND_HALF_DOWN
Rounds away from zero if the last significant digit is greater than or equal to 5, otherwise toward zero.
ROUND_HALF_EVEN
Like ROUND_HALF_DOWN except that if the value is 5 then the preceding digit is examined. Even values cause the result to be rounded down and odd digits cause the result to be rounded up.
ROUND_HALF_UP
Like ROUND_HALF_DOWN except if the last significant digit is 5 the value is rounded away from zero.
ROUND_UP
Round away from zero.
ROUND_05UP
Round away from zero if the last digit is 0 or 5, otherwise 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',
    ]

header_fmt = '{0:20} {1:^10} {2:^10} {3:^10}'

print 'POSITIVES:'
print

print header_fmt.format(' ', '1/8 (1)', '1/8 (2)', '1/8 (3)')
print header_fmt.format(' ', '-' * 10, '-' * 10, '-' * 10)
for rounding_mode in ROUNDING_MODES:
    print '{0:20}'.format(rounding_mode),
    for precision in [ 1, 2, 3 ]:
        context.prec = precision
        context.rounding = getattr(decimal, rounding_mode)
        value = decimal.Decimal(1) / decimal.Decimal(8)
        print '{0:<10}'.format(value),
    print

print
print 'NEGATIVES:'

print header_fmt.format(' ', '-1/8 (1)', '-1/8 (2)', '-1/8 (3)')
print header_fmt.format(' ', '-' * 10, '-' * 10, '-' * 10)
for rounding_mode in ROUNDING_MODES:
    print '{0:20}'.format(rounding_mode),
    for precision in [ 1, 2, 3 ]:
        context.prec = precision
        context.rounding = getattr(decimal, rounding_mode)
        value = decimal.Decimal(-1) / decimal.Decimal(8)
        print '{0:<10}'.format(value),
    print
$ python decimal_rounding.py
POSITIVES:

                      1/8 (1)    1/8 (2)    1/8 (3)
                     ---------- ---------- ----------
ROUND_CEILING        0.2        0.13       0.125
ROUND_DOWN           0.1        0.12       0.125
ROUND_FLOOR          0.1        0.12       0.125
ROUND_HALF_DOWN      0.1        0.12       0.125
ROUND_HALF_EVEN      0.1        0.12       0.125
ROUND_HALF_UP        0.1        0.13       0.125
ROUND_UP             0.2        0.13       0.125
ROUND_05UP           0.1        0.12       0.125

NEGATIVES:
                      -1/8 (1)   -1/8 (2)   -1/8 (3)
                     ---------- ---------- ----------
ROUND_CEILING        -0.1       -0.12      -0.125
ROUND_DOWN           -0.1       -0.12      -0.125
ROUND_FLOOR          -0.2       -0.13      -0.125
ROUND_HALF_DOWN      -0.1       -0.12      -0.125
ROUND_HALF_EVEN      -0.1       -0.12      -0.125
ROUND_HALF_UP        -0.1       -0.13      -0.125
ROUND_UP             -0.2       -0.13      -0.125
ROUND_05UP           -0.1       -0.12      -0.125

Local Context

Using Python 2.5 or later you can also apply the context to a subset of your code using a with statement and context manager.

import decimal

with decimal.localcontext() as c:
    c.prec = 2
    print 'Local precision:', c.prec
    print '3.14 / 3 =', (decimal.Decimal('3.14') / 3)

print
print 'Default precision:', decimal.getcontext().prec
print '3.14 / 3 =', (decimal.Decimal('3.14') / 3)
$ python decimal_context_manager.py
Local precision: 2
3.14 / 3 = 1.0

Default precision: 28
3.14 / 3 = 1.046666666666666666666666667

Per-Instance Context

Contexts can be used to construct Decimal instances, applying the precision and rounding arguments to the conversion from the input type. This lets your application select the precision of constant values separately from the precision of user data.

import decimal

# Set up a context with limited precision
c = decimal.getcontext().copy()
c.prec = 3

# Create our constant
pi = c.create_decimal('3.1415')

# The constant value is rounded off
print 'PI:', pi

# The result of using the constant uses the global context
print 'RESULT:', decimal.Decimal('2.01') * pi
$ python decimal_instance_context.py
PI: 3.14
RESULT: 6.3114

Threads

The “global” context is actually thread-local, so each thread can potentially be configured using different values.

import decimal
import threading
from Queue import Queue

class Multiplier(threading.Thread):
    def __init__(self, a, b, prec, q):
        self.a = a
        self.b = b
        self.prec = prec
        self.q = q
        threading.Thread.__init__(self)
    def run(self):
        c = decimal.getcontext().copy()
        c.prec = self.prec
        decimal.setcontext(c)
        self.q.put( (self.prec, a * b) )
        return

a = decimal.Decimal('3.14')
b = decimal.Decimal('1.234')
q = Queue()
threads = [ Multiplier(a, b, i, q) for i in range(1, 6) ]
for t in threads:
    t.start()

for t in threads:
    t.join()

for i in range(5):
    prec, value = q.get()
    print prec, '\t', value
$ python decimal_thread_context.py
1       4
2       3.9
3       3.87
4       3.875
5       3.8748

See also

decimal
The standard library documentation for this module.
Wikipedia: Floating Point
Article on floating point representations and arithmetic.

PyMOTW Home

The canonical version of this article


You might also be interested in:

News Topics

Recommended for You

Got a Question?