14
Decimal Precision and Rounding in 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:
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:
number = 0.976815352
rounded_number = (number, '.2f')
# or
print(f"rounded_number = {x:.2f}")
With decimal
:
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
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.
from decimal import Decimal
result = Decimal('3.1415926535') + Decimal('2.7182818285')
result = round(x, 2)
print(result)
# Output: 5.86
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.
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)
So better not to chose because of confusion.
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,
)
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))),
)
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
Thanks a lot to @jackiekazil !
All done!
14