Skip to content

ADR-0009: Money as Long minor units

  • Status: Accepted
  • Date: 2026-08-11
  • Deciders: maintainers
  • Related: money.md

Context

Money appears in parsing, storage, formatting, budgets, export/import, and comparisons. Floating point introduces rounding errors; strings invite duplication and inconsistent formatting.

Decision

All money is stored, parsed, formatted, and compared as Long minor units (sen/cents/paise) paired with an ISO currency code. Floating point is banned in the money path.

A single common/Currency object owns the currency tables: minor-digit count, display symbols, and detection tokens. MoneyFormatter, MoneyText, AmountParser, and the parser's currency detection all consume it — a currency is added in exactly one place.

Consequences

  • TransactionEntity.amountMillis, TransactionCandidate.amountMillis, BudgetEntity.amountMillis, corrections, and report slices are all Long minor units.
  • AmountParser converts text → minor units (never Double); JPY uses 0 minor digits.
  • Display formatting and editable-number round-tripping (MoneyFormatter / MoneyText) are pure Kotlin and unit-tested.
  • Export rows carry a decimal string + currency; import re-parses through AmountParser so minor digits apply symmetrically.

Alternatives considered

  • Double/Float — rejected: rounding errors on sums and comparisons.
  • BigDecimal everywhere — rejected: heavier, and DB storage would need text or scaled integers anyway.
  • Per-feature currency handling — rejected: drift; the tables centralize it.