Decimal fixed point and floating point arithmetic in Python

Floating point numbers are represented in the memory as a base 2 binary fraction. As a result floating point arithmetic operations can be weird at times. Addition of 0.1 and 0.2 can give annoying result as follows −
>>> 0.1 + 0.2
0.30000000000000004
In fact this is the nature of binary floating point representation. This is prevalent in any programming language. Python provides a decimal module to perform fast and correctly rounded floating-point arithmetic.
The decimal module is designed to represent floating points exactly as one would like them to behave, and arithmetic operation results are consistent with expectations. The precision level of representation and operation can be set upto 28 places.
The decimal module defines Decimal class. Decimal object can be declared by giving an integer, a string with numeric representation or a tuple as parameter to its constructor
>>> from decimal import Decimal
>>> d1 = Decimal(10)
>>> d1
Decimal('10')
>>> d2 = Decimal('10')
>>> d2
Decimal('10')
A tuple parameter contains three elements, sign (0 for positive, 1 for negative), a tuple of digits and the exponent. For example
>>> d3 = Decimal((1, (1, 2,3, 4), 2))
>>> d3
Decimal('-1.234E+5')
A more convenient way to represent floating point number of a specific precision is to obtain context environment of current thread by getcontext() finction and set the precision for Decimal object.
>>> from decimal import Decimal, getcontext
>>> getcontext().prec = 5
>>> d3 = Decimal(10)
>>> d4 = Decimal(3)
>>> d3/d4
Decimal('3.3333')
Contexts are environments for arithmetic operations used to determine precision and define rounding rules as well as limit the range for exponents.
decimal.getcontext() − Return the current context for the active thread.
decimal.setcontext(c) − Set the current context for the active thread to c.
Following rounding mode constants are defined in decimal module −
Following code snippet uses precision and rounding parameters of context object
>>> from decimal import *
>>> getcontext().prec = 5
>>> getcontext().rounding = ROUND_UP
>>> d1 = Decimal(100)
>>> d2 = Decimal(6)
>>> d1/d2
Decimal('16.667')
Arithmetic operations on Decimal objects
All usual arithmetic operations are done on Decimal objects, much like normal floats.
>>> a = Decimal('2.4')
>>> b = Decimal('1.2')
>>> a + b
Decimal('3.6')
>>> a - b
Decimal('1.2')
>>> b - a
Decimal('-1.2')
>>> a * b
Decimal('2.88')
>>> a / b
Decimal('2')
Arithmetic operation can be done on one Decimal operand and one integer operand. However with normal floating point object operations are invalid.
>>> a = Decimal('2.4')
>>> c = 2.1
>>> a + c
Traceback (most recent call last):
File "<pyshell#37>", line 1, in <module>
a+c
TypeError: unsupported operand type(s) for +: 'decimal.Decimal' and 'float'
Same exception occurs for all arithmetic operations.
The behavior of remainder (%) operator with Decimal object is slightly different from normal numeric types. Here, the sign of result is that of dividend rather than that of divisor
>>> -7%3
2
>>> 7%-3
-2
>>> Decimal(-7) % Decimal(3)
Decimal('-1')
>>> Decimal(7) % Decimal(-3)
Decimal('1')
Decimal.from_float() − This function converts normal float to Decimal object with exact binary representation. As a result from_float(0.1) and Decimal('0.1') are not same.
>>> d1 = Decimal('0.1')
>>> d2 = Decimal.from_float(0.1)
>>> d1,d2
(Decimal('0.1'), Decimal('0.1000000000000000055511151231257827021181583404541015625'))
This article explains use of functionality defined in decimal module of Python standard library.