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 allLongminor units.AmountParserconverts text → minor units (neverDouble); 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
AmountParserso minor digits apply symmetrically.
Alternatives considered
Double/Float— rejected: rounding errors on sums and comparisons.BigDecimaleverywhere — rejected: heavier, and DB storage would need text or scaled integers anyway.- Per-feature currency handling — rejected: drift; the tables centralize it.