Skip to content

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

FunctionSignature
addDecimals(a: string, b: string) => string | undefinedExact 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 | undefinedRound to a number of places
clampDecimal(value: string, min?: string, max?: string) => stringConstrain to a range
isDecimal(value: string) => booleanWhether the string is a canonical decimal
formatForLocale(value: string, locale: string, precision?: number) => stringFor display: grouping and the locale's separator
parseFromLocale(input: string, locale: string) => string | undefinedBack from what a reader typed
toEditable(value: string, locale?: string) => stringThe 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.