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.
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
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
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)
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,
)
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!
Top comments (0)