We’ve seen that floats in Python can sometimes be inaccurate due to roundoff error.  One way to correct this is to use Python’s Decimal data type. A Decimal variable allows the number of decimal points to be fixed, which can often eliminate roundoff error.

Decimals have a somewhat peculiar and clumsy syntax, however, so it’s worth looking at a few simple examples. The Decimal data type (with an uppercase ‘D’) is included in the decimal (with a lowercase ‘d’) module, so it must be imported before it can be used. [If you want to see the full contents of the decimal module, you can type help(decimal) in the console. However, be prepared for a very long list resulting from this. It’s probably more instructive to refer to the official documentation page on decimals.]

Intuitively, you might expect that simply feeding an ordinary float number into Decimal would convert it to a Decimal. To test this, try this in the console:

from decimal import *

The * in the first line imports everything from the decimal module.

See answer

The result, despite the huge number of significant digits, is certainly not exactly 0.1. However, if we enclose the 0.1 in quotes, we get:

from decimal import *

Now we get Decimal('0.1') as the result. Enclosing a number in quotes converts it to a string, so we see that in order to create a Decimal from a float, we must enclose the float in quotes to produce a string.

When we first import the decimal module, we can see its default context by calling getcontext(). The result is

Context(prec=28, rounding=ROUND_HALF_EVEN, Emin=-999999, Emax=999999, capitals=1, clamp=0, flags=[], traps=[InvalidOperation, DivisionByZero, Overflow])

The ‘prec’ is the precision, or number of decimal places, which defaults to 28.

An obvious application of Decimals is in keeping track of quantities of money, which are usually in decimal form, with 100 of some smaller unit, such as pence or cents, equal to a larger unit, such as a British pound or dollar. You might think that setting the ‘prec’ to 2 would allow precise calculations with money, but in fact, it doesn’t.

The problem is that ‘prec’ specifies the number of significant digits to retain, and not the number of digits following the decimal point. Thus, if we try

from decimal import *
getcontext().prec = 2
sum = Decimal(2.5) + Decimal(10.47)

we get the result Decimal('13') and not 12.97, as we might hope. We can solve this in two ways. First, we can enclose the quantities 2.5 and 10.47 in quotes to convert them to strings, and reset the prec to some large number. That is, we have

from decimal import *
getcontext().prec = 28
sum = Decimal('2.5') + Decimal('10.47')

This gives the desired result of Decimal('12.97').

Second, if we don’t want to write the arguments to Decimal as strings, we can use the quantize() function, as follows:

from decimal import *
sum = Decimal(2.5) + Decimal(10.47)
pence = Decimal('0.01')

This also gives the correct result of Decimal('12.97').

Despite their peculiarities, Decimals can be combined with other numeric types, although some caution is needed. You can perform the usual arithmetic operations combining Decimals and integers. For example, try the following (we’re assuming the prec is 28, the default):

from decimal import *
sum = Decimal('2.5') + Decimal('10.47')
sum * 12
sum ** 3
2 ** sum
See answers
sum * 12 -> Decimal(‘155.64’)
sum ** 3 -> Decimal(‘2181.825073’)
2 ** sum -> Decimal(‘8023.411077832104927916181357’)

Note that combining a Decimal with an integer always yields a Decimal result.

However, if you try combining a Decimal with a float, things go wrong. Try sum + 12.834, and you’ll get the error message “TypeError: unsupported operand type(s) for +: ‘decimal.Decimal’ and ‘float'”.  The error message means that the + operator is not defined when one of its operands is a Decimal and the other is a float.

To get around this, we can convert the Decimal to a float: float(sum) + 12.834.  As we’re now combining two floats, the answer is also a float: 25.804000000000002. As you can see, we’re back in the roundoff error problem again. If we want to avoid this, we can convert the float to a string and then to a Decimal, so we have sum + Decimal('12.834'), which now yields an exact Decimal result of Decimal('25.804'). If we try to convert the float to a Decimal without first enclosing it in quotes, we get roundoff error again. The statement sum3 + Decimal(12.834) gives us (assuming prec is set to its default value of 28): Decimal('25.80399999999999963051777740').


Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.