# Decimal Fixed Precision

## 02 July 2019 - 1 answer

I would like to use decimal in currency calculations, so I would like to work on exactly two numbers after comma. Initially I thought that `prec` of decimal's context refers to that property, but after a few experiments I feel a bit confused.

Experiment #1:

``````In [1]: import decimal

In [2]: decimal.getcontext().prec = 2

In [3]: a = decimal.Decimal('159.9')

In [4]: a
Out[4]: Decimal('159.9')

In [5]: b = decimal.Decimal('200')

In [6]: b
Out[6]: Decimal('200')

In [7]: b - a
Out[7]: Decimal('40')
``````

Experiment #2:

``````In [8]: decimal.getcontext().prec = 4

In [9]: a = decimal.Decimal('159.9')

In [10]: a
Out[10]: Decimal('159.9')

In [11]: b = decimal.Decimal('200')

In [12]: b
Out[12]: Decimal('200')

In [13]: b - a
Out[13]: Decimal('40.1')
``````

Experiment #3: (`prec` is still set to 4)

``````In [14]: a = decimal.Decimal('159999.9')

In [15]: a
Out[15]: Decimal('159999.9')

In [16]: b = decimal.Decimal('200000')

In [17]: b
Out[17]: Decimal('200000')

In [18]: b - a
Out[18]: Decimal('4.000E+4')
``````

Why does it work like in my examples? How should I work with decimals in my (currency calculations) case?

The precision sets the number of significant digits, which is not equivalent to the number of digits after the decimal point.

So if you have a precision of 2, you'll have two significant digits, so a number with 3 significant digits like `40.1` will be reduced to the upper two significant digits giving `40`.

There's no easy way to set the number of digits after the decimal point with `Decimal`. However you could use a high precision and always `round` your results to two decimals:

``````>>> from decimal import Decimal, getcontext
>>> getcontext().prec = 60  # use a higher/lower one if needed
>>> Decimal('200') - Decimal('159.9')
Decimal('40.1')
>>> r = Decimal('200') - Decimal('159.9')
>>> round(r, 2)
Decimal('40.10')
``````

The decimal FAQ also includes a similar question and answer (using `quantize`):

Q. In a fixed-point application with two decimal places, some inputs have many places and need to be rounded. Others are not supposed to have excess digits and need to be validated. What methods should be used?

A. The quantize() method rounds to a fixed number of decimal places. If the Inexact trap is set, it is also useful for validation:

``````>>> TWOPLACES = Decimal(10) ** -2       # same as Decimal('0.01')
>>> # Round to two places
>>> Decimal('3.214').quantize(TWOPLACES)
Decimal('3.21')
>>> # Validate that a number does not exceed two places
>>> Decimal('3.21').quantize(TWOPLACES, context=Context(traps=[Inexact]))
Decimal('3.21')
>>> Decimal('3.214').quantize(TWOPLACES, context=Context(traps=[Inexact]))
Traceback (most recent call last):
...
Inexact: None
``````

Q. Once I have valid two place inputs, how do I maintain that invariant throughout an application?

A. Some operations like addition, subtraction, and multiplication by an integer will automatically preserve fixed point. Others operations, like division and non-integer multiplication, will change the number of decimal places and need to be followed-up with a quantize() step:

``````>>> a = Decimal('102.72')           # Initial fixed-point values
>>> b = Decimal('3.17')
>>> a + b                           # Addition preserves fixed-point
Decimal('105.89')
>>> a - b
Decimal('99.55')
>>> a * 42                          # So does integer multiplication
Decimal('4314.24')
>>> (a * b).quantize(TWOPLACES)     # Must quantize non-integer multiplication
Decimal('325.62')
>>> (b / a).quantize(TWOPLACES)     # And quantize division
Decimal('0.03')
``````

In developing fixed-point applications, it is convenient to define functions to handle the quantize() step:

``````>>> def mul(x, y, fp=TWOPLACES):
...     return (x * y).quantize(fp)
>>> def div(x, y, fp=TWOPLACES):
...     return (x / y).quantize(fp)

>>> mul(a, b)                       # Automatically preserve fixed-point
Decimal('325.62')
>>> div(b, a)
Decimal('0.03')
``````