Composables
Decimal helpers
Exact arithmetic on decimal strings. Values are scaled to integers and computed with BigInt, so nothing passes through Number unless you ask it to. INumberInput is built on these.
+
addDecimals → 0.30
Number + Number → 0.30000000000000004
Binary floating point cannot represent most decimal fractions: 0.1 + 0.2 is not 0.3, and 10.00 becomes 10 as soon as it is a Number, losing the cents column.
The helpers
| Function | Signature | |
|---|---|---|
addDecimals | (a: string, b: string) => string | undefined | Exact addition; undefined if either side is invalid |
compareDecimals | (a: string, b: string) => number | -1, 0, 1, or NaN for invalid input |
roundDecimal | (value: string, precision: number) => string | undefined | Round to a number of places |
clampDecimal | (value: string, min?: string, max?: string) => string | Constrain to a range |
isDecimal | (value: string) => boolean | Whether the string is a canonical decimal |
formatForLocale | (value: string, locale: string, precision?: number) => string | For display: grouping and the locale's separator |
parseFromLocale | (input: string, locale: string) => string | undefined | Back from what a reader typed |
toEditable | (value: string, locale?: string) => string | The form a reader edits, without grouping |
localeSeparators | (locale: string) => { decimal, group } | The separators a locale uses |
Display and back
formatForLocale and parseFromLocale are a pair. The first is for showing a number — grouping separators, the locale's decimal mark. The second turns what someone typed back into a canonical decimal string, which is the only form the arithmetic accepts.
ts
formatForLocale('1234.5', 'de-DE', 2) // '1.234,50'
parseFromLocale('1.234,50', 'de-DE') // '1234.5'Keep the canonical form in your model and the locale form only on screen.