decimal – Fixed and floating point math¶
Purpose: | Decimal arithmetic using fixed and floating point numbers |
---|---|
Available In: | 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.1 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=[DivisionByZero, Overflow, InvalidOperation])
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 the with statement and a 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.