Decimal Precision and Rounding in Python
Tags: python
Published: 2021-06-28 12:44:47

---
title: Decimal Precision and Rounding in Python
published: true
date: 2021-05-13 00:00:00 UTC
tags: python
canonical_url: https://tech.serhatteker.com/post/2021-05/decimal-precision-rounding-python/
---

If you need precision in your project you should use `decimal` —[decimal module], since `float` data type has some issues and limitations as you can see here: [floating issues].

To use `decimal` as described here [decimal tutorial]:

```python
from decimal import Decimal


number = Decimal("0.976815352")
```

OK, if I want some specific precision what should I do? With `float` it's something like:

```python
number = 0.976815352
rounded_number = (number, '.2f')
# or
print(f"rounded_number = {x:.2f}")
```

With `decimal`:

```python
from decimal import getcontext, Decimal


getcontext().prec = 3
result = Decimal("0.976815352") / Decimal("2.42532")

print(result)
# 0.403
```

will give **0.403**. It seems OK. However if you try


```python
result = Decimal('3.1415926535') + Decimal('2.7182818285')

print(result)
# 5.86
```

will give **5.86** and you'll probably say *wth*. It's because in `decimal`, precision means *precision* including the digits before decimal/dot.

So how is it possible to know the digits of a decimal among thousands, e.g. price between ∞ and 0.00000001?

After a lot of researching, a.k.a. stackoverflowing, I came across to this: [gist].

## Precision & Rounding

### Option 1 - Round Decimal Using round()

```python
from decimal import Decimal


result = Decimal('3.1415926535') + Decimal('2.7182818285')
result = round(x, 2)

print(result)
# Output: 5.86
```

### Option 2 - Use Decimal Module's Roundings

```python
from decimal import Decimal, ROUND_HALF_UP
# All options for rounding:
# ROUND_05UP       ROUND_DOWN       ROUND_HALF_DOWN  ROUND_HALF_UP
# ROUND_CEILING    ROUND_FLOOR      ROUND_HALF_EVEN  ROUND_UP


result = Decimal('3.1415926535') + Decimal('2.7182818285')
result = Decimal(our_value.quantize(Decimal('.01'), rounding=ROUND_HALF_UP))

print(result)
# Output: 5.86
```

We use "0.01" as round to *2 digits* decimal.

### Option 3 - Setting Getcontext Precision

We saw this one in the beginning and it gave us *wth*:

```python
from decimal import getcontext, Decimal


getcontext().prec = 2
result = Decimal('3.1415926535') + Decimal('2.7182818285')

print(result)
```

So better not to chose because of confusion.

### Option 4 (Preferred Option)

After reading some comments on the [gist] I found my ultimate solution. It
comes from [py-moneyd] library:

```python
def round(self: M, ndigits: Optional[int] = 0) -> M:
    """
    Rounds the amount using the current ``Decimal`` rounding algorithm.
    """
    if ndigits is None:
        ndigits = 0
    return self.__class__(
        amount=self.amount.quantize(Decimal("1e" + str(-ndigits))),
        currency=self.currency,
    )
```

I did some tweaks for generalization and I wrote this *helper* method:

```python
from decimal import Decimal


class Acccounting:
    def round(self, amount: str, ndigits: int) -> Decimal:
        """ Rounds the amount using the current `Decimal` rounding algorithm. """
        if ndigits is None:
            ndigits = 0
        return Decimal(
            Decimal(amount).quantize(Decimal("1e" + str(-ndigits))),
        )
```

In order to round 2 with this *helper*:

```python
from decimal import Decimal
from .helpers import Acccounting


result = Decimal('3.1415926535') + Decimal('2.7182818285')
result = Acccounting().round(amount=result, ndigits=2)

print(result)
# Output: 5.86
```

Thanks a lot to [@jackiekazil] !

All done!




[decimal module]: https://docs.python.org/3/library/decimal.html#module-decimal
[floating issues]: https://docs.python.org/3/tutorial/floatingpoint.html#tut-fp-issues
[@jackiekazil]: https://gist.github.com/jackiekazil
[gist]: https://gist.github.com/jackiekazil/6201722
[py-moneyd]: https://github.com/py-moneyed/py-moneyed
[helper]: https://github.com/py-moneyed/py-moneyed/blob/7700e0bb64616f5386ec68fa58d0c0c66d8d6483/moneyed/classes.py#L262
[decimal tutorial]: https://docs.python.org/3/library/decimal.html#quick-start-tutorial