Background
The options-based formatting engine, CurrencyFormatter.formatWithOptions(amount: String, …), is Double-based end to end:
normalizedAmount.toDoubleOrNull() — CurrencyFormatter.kt:336
absAmount.toDoubleOrNull() — CurrencyFormatter.kt:418
formatDecimal(value: Double, …) rounds via round(value * factor) / factor — CurrencyFormatter.kt:599
So any input is reduced to Double before formatting. This includes the new kurrency-deci formatWithOptions(Deci, …) overloads: Deci.toString() is exact, but the shared engine immediately parses it to Double, so very large / high-precision Deci values are not formatted with exact decimal precision. (Surfaced in review of #13; documented there as a caveat.)
This is inconsistent with the minor-units API (0.3.1), which is deliberately string-based to avoid exactly this Double round-trip.
Impact
Low in practice — Double is exact for integers up to 2^53 (~90 trillion with 2 decimals), so normal currency amounts are unaffected. It matters only at extreme magnitude/precision, and specifically undercuts callers who chose Deci for guaranteed exactness.
Approaches
- Full engine (preferred): make
formatDecimal and the formatWithOptions path operate on a decimal string (parse → round half-up on the string → group → assemble) instead of Double. Benefits every formatWithOptions caller; this is a careful core refactor with regression risk (rounding, grouping, negatives), so it needs thorough cross-platform tests.
- Targeted (partial): route the
Deci overloads through the existing exact minor-units path — Deci → round to the currency's fraction scale → Long minor units → formatMinorUnitsWithOptions. Makes just the Deci overloads exact without touching the engine, but caps at Long range (~9.2e18 minor units) and needs a fallback above that.
Background
The options-based formatting engine,
CurrencyFormatter.formatWithOptions(amount: String, …), isDouble-based end to end:normalizedAmount.toDoubleOrNull()—CurrencyFormatter.kt:336absAmount.toDoubleOrNull()—CurrencyFormatter.kt:418formatDecimal(value: Double, …)rounds viaround(value * factor) / factor—CurrencyFormatter.kt:599So any input is reduced to
Doublebefore formatting. This includes the newkurrency-deciformatWithOptions(Deci, …)overloads:Deci.toString()is exact, but the shared engine immediately parses it toDouble, so very large / high-precisionDecivalues are not formatted with exact decimal precision. (Surfaced in review of #13; documented there as a caveat.)This is inconsistent with the minor-units API (0.3.1), which is deliberately string-based to avoid exactly this
Doubleround-trip.Impact
Low in practice —
Doubleis exact for integers up to 2^53 (~90 trillion with 2 decimals), so normal currency amounts are unaffected. It matters only at extreme magnitude/precision, and specifically undercuts callers who choseDecifor guaranteed exactness.Approaches
formatDecimaland theformatWithOptionspath operate on a decimal string (parse → round half-up on the string → group → assemble) instead ofDouble. Benefits everyformatWithOptionscaller; this is a careful core refactor with regression risk (rounding, grouping, negatives), so it needs thorough cross-platform tests.Decioverloads through the existing exact minor-units path —Deci→ round to the currency's fraction scale →Longminor units →formatMinorUnitsWithOptions. Makes just the Deci overloads exact without touching the engine, but caps atLongrange (~9.2e18 minor units) and needs a fallback above that.