Python’s decimal module provides exact decimal floating-point arithmetic. from decimal import Decimal, getcontext, localcontext, ROUND_HALF_UP, ROUND_HALF_EVEN, ROUND_DOWN, InvalidOperation. Decimal: Decimal("0.10") — ALWAYS from string to avoid float imprecision. Decimal(10) — from int is exact. Decimal(0.1) — from float inherits float’s imprecision. getcontext: getcontext().prec = 28 — set global precision. localcontext: with localcontext() as ctx: ctx.prec = 50 — thread-local override. quantize: Decimal("2.675").quantize(Decimal("0.01"), rounding=ROUND_HALF_UP) → Decimal(“2.68”). to_integral_value: Decimal("2.7").to_integral_value(rounding=ROUND_HALF_UP) → Decimal(“3”). ROUND_HALF_UP: 2.5 → 3.0 (everyday). ROUND_HALF_EVEN: 2.5 → 2.0, 3.5 → 4.0 (banker’s rounding, default). ROUND_DOWN / ROUND_FLOOR / ROUND_CEILING. as_tuple: Decimal("3.14").as_tuple() → DecimalTuple(sign=0, digits=(3,1,4), exponent=-2). compare: Decimal("NaN").is_nan(). InvalidOperation: raised on Decimal(“nan”) math. Decimal.from_float: exact representation. fma: Decimal.fma(d, other, third) — fused multiply-add. sqrt: Decimal("2").sqrt(). Claude Code generates money calculators, tax engines, invoice formatters, and currency converters.
CLAUDE.md for decimal
## decimal Stack
- Stdlib: from decimal import Decimal, getcontext, localcontext, ROUND_HALF_UP, ROUND_HALF_EVEN
- ALWAYS init from string: Decimal("0.10") — NEVER Decimal(0.10)
- Quantize: amount.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
- Precision: with localcontext() as ctx: ctx.prec = 50
- Money: store as Decimal; format with f"{amount:,.2f}"
- Errors: catch decimal.InvalidOperation for bad inputs
decimal Financial Calculation Pipeline
# app/money.py — Decimal, quantize, rounding, tax, invoice, currency
from __future__ import annotations
import decimal
from dataclasses import dataclass, field
from decimal import (
ROUND_CEILING,
ROUND_DOWN,
ROUND_FLOOR,
ROUND_HALF_DOWN,
ROUND_HALF_EVEN,
ROUND_HALF_UP,
ROUND_UP,
Decimal,
InvalidOperation,
getcontext,
localcontext,
)
from typing import Any, Iterable
# Global precision — 28 is Decimal default; 34 for IEEE 754-2008 128-bit
getcontext().prec = 28
# ─────────────────────────────────────────────────────────────────────────────
# 1. Decimal construction helpers
# ─────────────────────────────────────────────────────────────────────────────
TWO_PLACES = Decimal("0.01")
FOUR_PLACES = Decimal("0.0001")
ZERO = Decimal("0")
ONE = Decimal("1")
HUNDRED = Decimal("100")
def d(value: str | int | float | Decimal) -> Decimal:
"""
Safe Decimal constructor — always normalizes via string.
Example:
d("1.005") # Decimal("1.005")
d(42) # Decimal("42")
d(3.14) # from string repr, avoids float imprecision
"""
if isinstance(value, Decimal):
return value
if isinstance(value, float):
# Convert via string to avoid float garbage
return Decimal(str(value))
try:
return Decimal(str(value))
except InvalidOperation as exc:
raise ValueError(f"Cannot convert {value!r} to Decimal: {exc}") from exc
def parse_decimal(s: str, default: Decimal | None = None) -> Decimal | None:
"""
Parse a string to Decimal; return default on error.
Example:
parse_decimal(" $1,234.56 ") # Decimal("1234.56")
parse_decimal("n/a", default=ZERO) # Decimal("0")
"""
cleaned = s.strip().lstrip("$").replace(",", "").replace(" ", "")
try:
return Decimal(cleaned)
except InvalidOperation:
return default
# ─────────────────────────────────────────────────────────────────────────────
# 2. Rounding helpers
# ─────────────────────────────────────────────────────────────────────────────
def round_money(
amount: Decimal,
places: int = 2,
rounding: str = ROUND_HALF_UP,
) -> Decimal:
"""
Round to a fixed number of decimal places.
Example:
round_money(Decimal("2.675")) # Decimal("2.68")
round_money(Decimal("1.005"), rounding=ROUND_HALF_EVEN) # Decimal("1.00") banker's
"""
quantizer = Decimal(10) ** -places
return amount.quantize(quantizer, rounding=rounding)
def round_half_even(amount: Decimal, places: int = 2) -> Decimal:
"""Banker's rounding (round half to even) — reduces statistical bias."""
return round_money(amount, places, ROUND_HALF_EVEN)
def round_up(amount: Decimal, places: int = 2) -> Decimal:
"""Always round up (ceiling for positives)."""
return round_money(amount, places, ROUND_CEILING)
def round_down(amount: Decimal, places: int = 2) -> Decimal:
"""Always round down (floor for positives)."""
return round_money(amount, places, ROUND_FLOOR)
def to_int(amount: Decimal, rounding: str = ROUND_HALF_UP) -> int:
"""Round a Decimal to the nearest integer."""
return int(amount.to_integral_value(rounding=rounding))
# ─────────────────────────────────────────────────────────────────────────────
# 3. Money arithmetic
# ─────────────────────────────────────────────────────────────────────────────
@dataclass
class Money:
"""
Immutable money value with 2-decimal precision and a currency code.
Example:
price = Money("19.99", "USD")
tax = price.apply_rate(Decimal("0.08"))
total = price + tax
print(total) # "21.59 USD"
"""
amount: Decimal
currency: str = "USD"
def __post_init__(self) -> None:
object.__setattr__(self, "amount", round_money(d(self.amount))) # type: ignore[arg-type]
def __add__(self, other: "Money") -> "Money":
self._check_currency(other)
return Money(self.amount + other.amount, self.currency)
def __sub__(self, other: "Money") -> "Money":
self._check_currency(other)
return Money(self.amount - other.amount, self.currency)
def __mul__(self, factor: Decimal | int | str | float) -> "Money":
return Money(self.amount * d(factor), self.currency)
def __truediv__(self, divisor: Decimal | int | str | float) -> "Money":
return Money(self.amount / d(divisor), self.currency)
def __neg__(self) -> "Money":
return Money(-self.amount, self.currency)
def __str__(self) -> str:
return f"{self.amount:,.2f} {self.currency}"
def apply_rate(self, rate: Decimal | str, places: int = 2) -> "Money":
"""Multiply by a rate and round."""
return Money(round_money(self.amount * d(rate), places), self.currency)
def _check_currency(self, other: "Money") -> None:
if self.currency != other.currency:
raise ValueError(f"Currency mismatch: {self.currency} vs {other.currency}")
def split_evenly(total: Decimal, n: int, places: int = 2) -> list[Decimal]:
"""
Split total into n equal parts, distributing remainder cents evenly.
Example:
split_evenly(Decimal("10.00"), 3) # [Decimal("3.34"), Decimal("3.33"), Decimal("3.33")]
"""
if n <= 0:
raise ValueError("n must be positive")
unit = Decimal(10) ** -places
per = round_down(total / n, places)
rem = round_money(total - per * n, places)
pieces = [per] * n
steps = int(rem / unit)
for i in range(steps):
pieces[i] += unit
return pieces
# ─────────────────────────────────────────────────────────────────────────────
# 4. Financial calculations
# ─────────────────────────────────────────────────────────────────────────────
def apply_tax(amount: Decimal, tax_rate_pct: Decimal) -> tuple[Decimal, Decimal]:
"""
Compute tax and total. Returns (tax_amount, total).
Example:
tax, total = apply_tax(Decimal("100.00"), Decimal("8.5"))
# (Decimal("8.50"), Decimal("108.50"))
"""
tax = round_money(amount * tax_rate_pct / HUNDRED)
total = amount + tax
return tax, total
def apply_discount(
amount: Decimal,
discount_pct: Decimal,
) -> tuple[Decimal, Decimal]:
"""
Apply a percentage discount. Returns (discount_amount, discounted_price).
Example:
disc, price = apply_discount(Decimal("120.00"), Decimal("25"))
# (Decimal("30.00"), Decimal("90.00"))
"""
disc = round_money(amount * discount_pct / HUNDRED)
after = amount - disc
return disc, after
def compound_interest(
principal: Decimal,
annual_rate_pct: Decimal,
years: int,
compounds_per_year: int = 12,
precision: int = 10,
) -> Decimal:
"""
Compute compound interest final amount. A = P(1 + r/n)^(nt).
Example:
final = compound_interest(Decimal("1000"), Decimal("5"), years=10)
# Decimal("1647.0094563...")
"""
with localcontext() as ctx:
ctx.prec = precision
r = annual_rate_pct / HUNDRED
n = Decimal(compounds_per_year)
nt = Decimal(years * compounds_per_year)
result = principal * (ONE + r / n) ** nt
return round_money(result, 10)
def percentage_change(old: Decimal, new: Decimal) -> Decimal:
"""
Compute percentage change from old to new.
Example:
percentage_change(Decimal("80"), Decimal("100")) # 25.00
"""
if old == ZERO:
raise ZeroDivisionError("old value is zero")
return round_money((new - old) / abs(old) * HUNDRED)
# ─────────────────────────────────────────────────────────────────────────────
# 5. Invoice builder
# ─────────────────────────────────────────────────────────────────────────────
@dataclass
class LineItem:
description: str
quantity: Decimal
unit_price: Decimal
@property
def subtotal(self) -> Decimal:
return round_money(self.quantity * self.unit_price)
@dataclass
class Invoice:
"""
A simple invoice with line items, discount, and tax.
Example:
inv = Invoice(tax_rate_pct=Decimal("8.5"), discount_pct=Decimal("10"))
inv.add("Widget A", 3, "12.99")
inv.add("Widget B", 1, "49.00")
print(inv.summary())
"""
tax_rate_pct: Decimal = ZERO
discount_pct: Decimal = ZERO
currency: str = "USD"
items: list[LineItem] = field(default_factory=list)
def add(
self,
description: str,
quantity: int | str | Decimal,
unit_price: str | Decimal,
) -> "Invoice":
self.items.append(LineItem(
description=description,
quantity=d(quantity),
unit_price=d(unit_price),
))
return self
@property
def subtotal(self) -> Decimal:
return sum((item.subtotal for item in self.items), ZERO)
@property
def discount_amount(self) -> Decimal:
return round_money(self.subtotal * self.discount_pct / HUNDRED)
@property
def taxable(self) -> Decimal:
return self.subtotal - self.discount_amount
@property
def tax_amount(self) -> Decimal:
return round_money(self.taxable * self.tax_rate_pct / HUNDRED)
@property
def total(self) -> Decimal:
return self.taxable + self.tax_amount
def summary(self) -> str:
lines = ["Invoice"]
for item in self.items:
lines.append(
f" {item.description:30s} {item.quantity:>6} × "
f"{item.unit_price:>10,.2f} = {item.subtotal:>10,.2f} {self.currency}"
)
lines += [
f" {'Subtotal':42s} {self.subtotal:>10,.2f} {self.currency}",
]
if self.discount_pct:
lines.append(
f" {'Discount (' + str(self.discount_pct) + '%)':42s} "
f"-{self.discount_amount:>9,.2f} {self.currency}"
)
if self.tax_rate_pct:
lines.append(
f" {'Tax (' + str(self.tax_rate_pct) + '%)':42s} "
f"+{self.tax_amount:>9,.2f} {self.currency}"
)
lines.append(f" {'TOTAL':42s} {self.total:>10,.2f} {self.currency}")
return "\n".join(lines)
# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
print("=== decimal demo ===")
print("\n--- why string construction matters ---")
print(f" float 0.1 + 0.2 = {0.1 + 0.2}")
print(f" Decimal('0.1') + Decimal('0.2') = {Decimal('0.1') + Decimal('0.2')}")
print("\n--- rounding modes ---")
amount = Decimal("2.675")
print(f" ROUND_HALF_UP: {round_money(amount, rounding=ROUND_HALF_UP)}")
print(f" ROUND_HALF_EVEN: {round_money(amount, rounding=ROUND_HALF_EVEN)}")
print(f" ROUND_DOWN: {round_money(amount, rounding=ROUND_DOWN)}")
print("\n--- Money ---")
price = Money("19.99")
qty = 3
sub = price * qty
tax = sub.apply_rate(Decimal("0.085"))
total = sub + tax
print(f" price: {price}")
print(f" subtotal: {sub}")
print(f" tax: {tax}")
print(f" total: {total}")
print("\n--- split_evenly ---")
parts = split_evenly(Decimal("10.00"), 3)
print(f" $10 / 3: {parts} sum={sum(parts)}")
print("\n--- apply_tax ---")
tax, total = apply_tax(Decimal("100.00"), Decimal("8.5"))
print(f" tax={tax} total={total}")
print("\n--- apply_discount ---")
disc, after = apply_discount(Decimal("120.00"), Decimal("25"))
print(f" disc={disc} after={after}")
print("\n--- compound_interest ---")
final = compound_interest(Decimal("1000"), Decimal("5"), years=10)
print(f" $1000 at 5% for 10 years: {final:.4f}")
print("\n--- percentage_change ---")
pct = percentage_change(Decimal("80"), Decimal("100"))
print(f" 80 → 100: {pct}%")
print("\n--- Invoice ---")
inv = (
Invoice(tax_rate_pct=Decimal("8.5"), discount_pct=Decimal("10"))
.add("Professional Services", 8, "150.00")
.add("Software License", 1, "299.00")
.add("Support (annual)", 1, "199.00")
)
print(inv.summary())
print("\n--- parse_decimal ---")
for raw in [" $1,234.56 ", "99.99", "n/a", "1_000"]:
print(f" {raw!r:20s} → {parse_decimal(raw)}")
print("\n=== done ===")
For the fractions.Fraction alternative — fractions.Fraction provides exact rational arithmetic using arbitrary-precision integers with no rounding at all: Fraction(1, 3) + Fraction(1, 6) = Fraction(1, 2) exactly; decimal.Decimal uses fixed-precision base-10 floating-point with configurable rounding — use Fraction when exact rational math is required (splitting algorithms, continued fractions, symbolic computation), decimal.Decimal for financial applications where base-10 representation, configurable rounding modes, and two-decimal currency semantics are the requirement. For the money / py-moneyed alternative — third-party money libraries (PyPI money, py-moneyed, moneyed) provide Money(amount, currency) objects with multi-currency arithmetic, locale-aware formatting, and exchange rate hooks; stdlib decimal.Decimal has zero dependencies and gives you the arithmetic primitives — use a money library for multi-currency applications needing exchange rate integration and locale formatting, stdlib Decimal for single-currency financial services, billing engines, and anywhere adding a dependency is costly. The Claude Skills 360 bundle includes decimal skill sets covering d()/parse_decimal() safe constructors, round_money()/round_half_even()/round_up()/round_down()/to_int() rounding helpers, Money dataclass with arithmetic operators and apply_rate(), split_evenly() for even bill-splitting with cent distribution, apply_tax()/apply_discount()/compound_interest()/percentage_change() financial functions, and Invoice/LineItem builder with summary(). Start with the free tier to try financial calculation and decimal pipeline code generation.