AI Skill Report Card
Using Decimal Arithmetic
Quick Start14 / 15
Pythonfrom decimal import Decimal, getcontext, ROUND_HALF_UP # Create Decimals from strings (not floats!) for exactness price = Decimal("19.99") tax_rate = Decimal("0.075") tax = (price * tax_rate).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP) total = price + tax print(total) # Decimal('21.49') # Compare: float arithmetic introduces error print(0.1 + 0.2) # 0.30000000000000004 print(Decimal("0.1") + Decimal("0.2")) # 0.3
Recommendation▾
Add an example showing a bad outcome from a real bug scenario (e.g., what happens if float is used for money over many transactions, showing accumulated error)
Workflow14 / 15
Progress:
- Step 1: Identify why float is insufficient (money, exact decimal rules, auditability)
- Step 2: Construct Decimals from strings or integers, never from floats directly
- Step 3: Set global context precision if defaults (28 digits) are insufficient
- Step 4: Perform arithmetic — operators (
+,-,*,/) work naturally - Step 5: Round/quantize results to the desired number of decimal places
- Step 6: Choose an explicit rounding mode matching business/legal requirements
- Step 7: Handle exceptional conditions (DivisionByZero, InvalidOperation, Overflow) if inputs are untrusted
- Step 8: Use
localcontext()for temporary precision/rounding changes scoped to a block
Step 1: Constructing Decimals correctly
Python# CORRECT — exact d1 = Decimal("0.1") d2 = Decimal(10) d3 = Decimal((0, (3, 1, 4), -2)) # sign, digits tuple, exponent -> 3.14 # WRONG — inherits binary float imprecision d_bad = Decimal(0.1) # Decimal('0.1000000000000000055511151231257827021181583404541015625')
Step 2: Setting precision
Pythonfrom decimal import getcontext getcontext().prec = 50 # global precision (significant digits), default 28
Step 3: Scoped precision with localcontext
Pythonfrom decimal import localcontext with localcontext() as ctx: ctx.prec = 6 result = Decimal(1) / Decimal(7) # only affects this block # outside the block, precision reverts to previous setting
Step 4: Rounding and quantizing
Pythonfrom decimal import ( ROUND_CEILING, ROUND_FLOOR, ROUND_HALF_UP, ROUND_HALF_EVEN, ROUND_DOWN, ROUND_UP, ROUND_05UP ) value = Decimal("2.675") value.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP) # 2.68 value.quantize(Decimal("0.01"), rounding=ROUND_HALF_EVEN) # 2.67 (banker's rounding, module default)
Step 5: Handling exceptions
Pythonfrom decimal import Decimal, DivisionByZero, InvalidOperation, localcontext with localcontext() as ctx: ctx.traps[DivisionByZero] = True # raise instead of returning Infinity try: Decimal(1) / Decimal(0) except DivisionByZero: print("cannot divide by zero")
Recommendation▾
Include guidance on serialization/deserialization with JSON since Decimal isn't natively JSON-serializable, a common real-world pitfall
Examples16 / 20
Example 1: Currency total with tax Input:
Pythonitems = [Decimal("12.50"), Decimal("7.25"), Decimal("3.10")] subtotal = sum(items) tax = (subtotal * Decimal("0.08")).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
Output:
subtotal = Decimal('22.85')
tax = Decimal('1.83')
Example 2: Setting precision for scientific-style computation Input:
Pythonfrom decimal import Decimal, localcontext with localcontext() as ctx: ctx.prec = 10 result = Decimal(22) / Decimal(7)
Output:
result = Decimal('3.142857143')
Example 3: Comparing Decimal to float safely Input:
PythonDecimal("0.1") == 0.1
Output:
False # never mix Decimal and float directly in comparisons/arithmetic;
# convert float via str first: Decimal(str(0.1))
Recommendation▾
The workflow checklist and step-by-step 'Step 1-5' headers duplicate content awkwardly (checklist has 8 steps but only 5 are detailed) — align these for clarity
Best Practices
- Always construct
Decimalfrom a string or integer; converting fromfloatpropagates binary imprecision. - Use
quantize()for fixed-point rounding to a specific number of decimal places (e.g., cents). - Prefer
ROUND_HALF_UPfor typical financial rounding,ROUND_HALF_EVEN(default) for statistically unbiased rounding. - Use
localcontext()to scope temporary precision/rounding changes instead of mutating the global context permanently. - Set
prechigh enough for intermediate calculations; only round/quantize the final displayed or stored value. - Never mix
Decimalandfloatin arithmetic or equality comparisons — convert explicitly first. - Use
Decimal.is_nan(),.is_infinite(),.is_zero()for safe special-value checks instead of comparisons. - For serialization, use
str(decimal_value)to preserve exact representation.
Common Pitfalls
- Passing a
floatliteral toDecimal()(e.g.,Decimal(0.1)) — silently imports float's imprecision. - Forgetting that
getcontext().precsets significant digits, not decimal places — usequantize()for decimal-place control. - Assuming
round()behaves likequantize()—round()on Decimal follows context rounding but doesn't fix exponent/decimal places the same way. - Ignoring
InvalidOperation,Overflow, orDivisionByZerotraps when processing untrusted input, causing NaN/Infinity to propagate silently. - Mutating the global context (
getcontext().prec = X) instead of usinglocalcontext(), causing precision leaks across unrelated code. - Comparing
Decimalandfloatdirectly, which can raise errors or produce misleading equality results.