Skip to Content
Course content

222: The decimal Module for Exact Arithmetic

Click on the "Edit" button in the top corner of the screen to edit your slide content.

If you've ever built a financial app or even a simple shopping cart, you've probably encountered that weird moment where 0.1 + 0.2 doesn't actually equal 0.3. It's frustrating, but it's not a Python bug—it's how binary floating-point math works. For most things, a tiny rounding error doesn't matter. But when you're dealing with money, those fractions of a cent add up, and your accountant will eventually come for your head.

Let's build a tiny invoice calculator for a freelance project to see how we can solve this using the decimal module.

The floating point trap

I'll start by doing this the "obvious" way using standard floats. I have a project fee of $1,200.50 and a sales tax of 7%.

fee = 1200.50
tax_rate = 0.07
total = fee + (fee * tax_rate)
print(f"Total: {total}")
# Output: Total: 1284.535

That looks okay so far. But let's say I want to check if the total is exactly 1284.535 for some validation logic. In a larger system with more additions and subtractions, you'll start seeing things like 1284.5350000000002. It's a nightmare to debug because the error is microscopic but persistent.

Switching to Decimal

To fix this, we use the Decimal type. The key here is that Decimal represents numbers as base-10, exactly how we write them on paper. I'm going to rewrite the calculator now.

from decimal import Decimal

fee = Decimal(1200.50)
tax_rate = Decimal(0.07)
total = fee + (fee * tax_rate)
print(f"Total: {total}")
# Wait... this didn't actually fix the precision!

I just made the classic mistake. If you look at the output of the code above, you'll see a long string of decimals. Why? Because I passed float values into the Decimal constructor. When I wrote Decimal(1200.50), Python first created a float (with all its inherent imprecision) and then passed that imperfect number into Decimal. It's like trying to fix a blurry photo by scanning it into a high-res scanner—the blur is already there.

The rule is: Always pass strings to Decimal.

from decimal import Decimal

# Now we're doing it right
fee = Decimal('1200.50')
tax_rate = Decimal('0.07')
total = fee + (fee * tax_rate)
print(f"Total: {total}") 
# Output: Total: 1284.535

Locking down the rounding

In a real invoice, we can't charge someone 0.535 cents. We need to round to two decimal places. While you could use the round() function, the decimal module gives us quantize(), which is far more powerful because it lets us explicitly define the rounding strategy (like rounding up, down, or to the nearest even number).

I prefer ROUND_HALF_UP for financial work because it's what most people expect from school math.

from decimal import Decimal, ROUND_HALF_UP

fee = Decimal('1200.50')
tax_rate = Decimal('0.07')
total = fee + (fee * tax_rate)

# '0.01' tells Decimal we want two decimal places
final_total = total.quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
print(f"Final Invoice Amount: ${final_total}")
# Output: Final Invoice Amount: $1284.54

Now we have a result that is mathematically exact and business-compliant. I've used a string for the precision mask ('0.01') to tell Python exactly where I want the cutoff to be. It's a bit more verbose than floats, but in production code, that clarity is a lifesaver.




📋 Practical Task

Build a Precision-Based Currency Converter

You are building a tool to convert USD to EUR. The current exchange rate is 0.923456. You need to ensure that the conversion is exact and that the final result is rounded to exactly two decimal places using the ROUND_HALF_UP method.

Requirements:

  • Create a variable usd_amount as a Decimal (use the value '150.75').
  • Create a variable exchange_rate as a Decimal (use the value '0.923456').
  • Calculate the eur_total.
  • Use quantize() to round the eur_total to two decimal places.
  • Print the final result in the format: "Converted Total: €XXX.XX".
Rating
0 0

There are no comments for now.

to be the first to leave a comment.