DEV Community

Serhat Teker
Serhat Teker

Posted on • Originally published at tech.serhatteker.com on

Decimal Precision and Rounding in Python

If you need precision in your project you should use decimaldecimal 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:

from decimal import Decimal


number = Decimal("0.976815352")
Enter fullscreen mode Exit fullscreen mode

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

number = 0.976815352
rounded_number = (number, '.2f')
# or
print(f"rounded_number = {x:.2f}")
Enter fullscreen mode Exit fullscreen mode

With decimal:

from decimal import getcontext, Decimal


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

print(result)
# 0.403
Enter fullscreen mode Exit fullscreen mode

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

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

print(result)
# 5.86
Enter fullscreen mode Exit fullscreen mode

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()

from decimal import Decimal


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

print(result)
# Output: 5.86
Enter fullscreen mode Exit fullscreen mode

Option 2 - Use Decimal Module's Roundings

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
Enter fullscreen mode Exit fullscreen mode

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:

from decimal import getcontext, Decimal


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

print(result)
Enter fullscreen mode Exit fullscreen mode

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:

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,
    )
Enter fullscreen mode Exit fullscreen mode

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

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))),
        )
Enter fullscreen mode Exit fullscreen mode

In order to round 2 with this helper:

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
Enter fullscreen mode Exit fullscreen mode

Thanks a lot to @jackiekazil !

All done!

Top comments (0)