Python
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax and Data Types
-
Section 3: Collections
-
39: Set Operations: Union, Intersection, Difference
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Turtle Graphics and Early Practice Projects
-
Section 7: Working with Files and I/O
-
Section 8: Regular Expressions
-
Section 9: Object-Oriented Python
-
Section 10: Error Handling
-
Section 11: Modules and Packages
-
Section 12: Iterators, Generators, and Functional Tools
-
Section 13: Decorators and Metaprogramming
-
Section 14: Concurrency and Parallelism
-
Section 15: Working with Dates, Times, and Numbers
-
Section 16: Standard Library Deep Dive I: Data Structures
-
Section 17: Standard Library Deep Dive II: System and Introspection
-
Section 18: Standard Library Deep Dive III: Security and Encoding
-
Section 19: Standard Library Deep Dive IV: Text and Data Utilities
-
Section 20: Networking and Web Basics
-
Section 21: Working with Databases
-
Section 22: Testing and Quality
-
Section 23: Advanced Typing
-
Section 24: Context Managers and Resource Handling
-
Section 25: Text, Unicode, and Binary Data
-
Section 26: More Functional and Iteration Tools
-
Section 27: Data Validation and Configuration
-
Section 28: Working with Images and Media
-
Section 29: Property-Based and Documentation Testing
-
Section 30: Packaging and Deployment
-
Section 31: Performance and Internals
-
Section 32: Design Patterns in Python
-
Section 33: GUI Programming
-
Section 34: Security Basics
-
Section 35: Data Structures and Algorithms
-
Section 36: Practical Projects
-
Section 37: Capstone Projects
-
Section 38: Interview and Algorithm Practice
-
Section 39: Writing Idiomatic Python
222: The decimal Module for Exact Arithmetic
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_amountas aDecimal(use the value'150.75'). - Create a variable
exchange_rateas aDecimal(use the value'0.923456'). - Calculate the
eur_total. - Use
quantize()to round theeur_totalto two decimal places. - Print the final result in the format:
"Converted Total: €XXX.XX".
There are no comments for now.