Decimal fixed point and floating point arithmetic in Python

PythonProgrammingServer Side Programming

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

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
>>> d2 = Decimal('10')
>>> d2

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

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

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 −

ROUND_CEILINGRound towards Infinity.
ROUND_DOWNRound towards zero.
ROUND_FLOORRound towards -Infinity.
ROUND_HALF_DOWNRound to nearest with ties going towards zero.
ROUND_HALF_EVENRound to nearest with ties going to nearest even integer.
ROUND_HALF_UPRound to nearest with ties going away from zero.
ROUND_UPRound away from zero.
ROUND_05UPRound away from zero if last digit after rounding towards zero would have been 0 or 5; otherwise round towards zero.

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

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
>>> a - b
>>> b - a
>>> a * b
>>> a / b

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>
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
>>> 7%-3
>>> Decimal(-7) % Decimal(3)
>>> Decimal(7) % Decimal(-3)

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.

Published on 27-Dec-2018 08:24:00