# ADR 0003 — Monetary amounts: exact decimals everywhere, rounding is jurisdiction policy - **Status**: Accepted — 2026-08-05 - **Context**: docs/research/ledger-engines.md (float hazards, TigerBeetle fixed-point design) and docs/research/canada-gst-qst.md (statutory rounding). ## Decision 1. **No floats, anywhere, ever.** In JSON/YAML, every amount, quantity, and rate is a **string** ("19.99"); in code it is `decimal.Decimal` wrapped in the `Money` type (core/money.py). Construction from `float` raises at every boundary: `Money`, the Pydantic schema (`ExactDecimal`), and the ALSL loader all reject floats independently. 2. **Rounding is a named, per-jurisdiction ALSL policy — not an engine default.** Research falsified the assumption that banker's rounding is universal: GST/QST rounding is **half-up** by statute (Excise Tax Act s. 165.2(2): fractions < $0.005 disregarded, ≥ $0.005 deemed one cent; Revenu Québec IN-203-V states the same for QST). `RoundingMode` therefore supports `half_up` and `half_even`, and the ca-qc policy set selects `half_up` with a source citation. 3. **Round late.** Intermediate computations (qty × unit price, base × rate, amount × fx rate) keep full precision; quantization to the currency's ISO 4217 minor unit happens once, when a figure becomes a posted amount — and each quantization is recorded in the provenance graph's operation string (e.g. `tax:QST@0.09975~half_up`). ## Consequences - Cross-currency arithmetic is a hard error (`CurrencyMismatchError`); conversion only happens in the FX pass, explicitly, with a cited rate. - Determinism holds bit-for-bit: same document + same policies = identical journal (property-tested in tests/property/).