Number Formatting
Number Formatting
Formatting is configured via an IFormat object, accepted in three places:
fmt(value, format)— direct formattingcalc(expr, { ..., _fmt })— format the expression result- Chain terminator
chainAdd(...)(format)
Every field has full type hints and autocompletion — typos are caught at compile time.
fmt(1234.5, { decimals: 2, thousands: true }) // "1,234.50"
calc('9.9 * 3', { _fmt: { decimals: 2 } }) // "29.70"
chainAdd(1000, 2000)({ decimals: 2, thousands: 'eu' }) // "3.000,00"
Full field reference
| Field | Type | Description |
|---|---|---|
decimals | number | { min?, max? } | Decimal places (default: preserve original): • number = fixed N places, zero-padded ( 2→"1.00")• { max } = at most N places, excess truncated, no zero-padding• { min } = at least N places, zero-padded |
rounding | 'truncate' | 'halfUp' | 'banker' | 'ceil' | 'floor' | 'expand' 'trunc' | 'round' | 'halfEven' | Rounding strategy used with decimals, default 'truncate':• halfUp / round — round half up• banker / halfEven — banker's rounding (round half to even)• ceil / floor — toward +∞ / -∞ (like Math)• expand — away from zeroSee below |
thousands | boolean | 'us' | 'eu' | 'in' | Thousands grouping (default: none): • true = US style 1,234.50• 'eu' = European 1.234,50• 'in' = Indian 1,23,456 |
compact | boolean | 'zh' | Compact notation (default: none): • true = K/M/B/T (1.23M)• 'zh' = 万/亿 (123.45万) |
clamp | [min, max] | Clamp result to range: values below min become min, above max become max(applied before formatting) |
output | 'percent' | 'fraction' | 'scientific' | 'number'(or token %% // e num) | Output form, mutually exclusive: • percent — multiply by 100 and append %• fraction — simplest fraction• scientific — scientific notation• number — return a number (all others return string) |
plus | boolean | Prepend + to positive numbers(negatives and zero are unaffected) |
pad | number | Left-pad the integer part with zeros to N digits ( 5 with pad: 3 → "005", decimal part unaffected) |
Decimal places decimals
A number means fixed places (zero-padded); an object means a range (max = at most, min = at least):
fmt(0.1234, { decimals: 2 }) // "0.12" fixed 2 places
fmt(1.1, { decimals: { max: 2 } }) // "1.1" at most 2, no zero-padding
fmt(1, { decimals: { min: 2 } }) // "1.00" at least 2 places
fmt(1.2345, { decimals: { min: 1, max: 3 } }) // "1.234"
Rounding rounding
Default is truncate (toward zero, no precision loss). Each strategy has a JS-style alias — use whichever is easier to remember:
| Value | JS alias | Meaning |
|---|---|---|
'truncate' | 'trunc' | Truncate toward zero (same as Math.trunc), default |
'halfUp' | 'round' | Round half up (same as Math.round) |
'banker' | 'halfEven' | Banker's rounding: round half to even (same as Intl.NumberFormat with roundingMode: 'halfEven') |
'ceil' | — | Round toward +∞ (same as Math.ceil) |
'floor' | — | Round toward -∞ (same as Math.floor) |
'expand' | — | Round away from zero |
The three directional modes differ only for negative values (-1.1 → ceil -1, floor -2, expand -2). For positive values they coincide with rounding up.
fmt(1.235, { decimals: 2 }) // "1.23" default truncate
fmt(1.235, { decimals: 2, rounding: 'round' }) // "1.24" round half up
fmt(2.5, { decimals: 0, rounding: 'halfEven' }) // "2" banker's: round to even
fmt(3.5, { decimals: 0, rounding: 'halfEven' }) // "4" round to even
fmt(-1.001, { decimals: 0, rounding: 'ceil' }) // "-1" toward +∞
fmt(-1.001, { decimals: 0, rounding: 'floor' }) // "-2" toward -∞
fmt(-1.001, { decimals: 0, rounding: 'expand' }) // "-2" away from zero
Number.prototype.toFixed is not banker's rounding (it approximates round-half-up and has floating-point bugs, e.g. (1.005).toFixed(2) → "1.00"). Use 'banker' / 'halfEven' for true banker's rounding.Thousands separator thousands
true = US style; or specify a locale preset:
fmt(1234.5, { decimals: 2, thousands: true }) // "1,234.50" (US)
fmt(1234.5, { decimals: 2, thousands: 'eu' }) // "1.234,50" (European)
fmt(123456, { thousands: 'in' }) // "1,23,456" (Indian)
Compact notation compact
true = K/M/B/T; or 'zh' for Chinese units:
fmt(1234567, { decimals: 2, compact: true }) // "1.23M"
fmt(1234567, { decimals: 2, compact: 'zh' }) // "123.45万"
Output form output
Mutually exclusive output forms — use either the readable word or the token symbol:
fmt(0.5, { output: 'percent' }) // "50%" (or '%%')
fmt(0.5, { output: 'fraction' }) // "1/2" (or '//')
fmt(1000, { output: 'scientific' }) // "1e+3" (or 'e')
fmt(0.1234, { output: 'number', decimals: 2 }) // 0.12 ← number type (or 'num')
Clamping clamp
Clamp the result to [min, max] — values below the lower bound become the lower bound, values above the upper bound become the upper bound. Composable with other fields:
calc('150', { _fmt: { clamp: [0, 100] } }) // "100"
calc('-50', { _fmt: { clamp: [0, 100] } }) // "0"
calc('150.567', { _fmt: { clamp: [0, 100], decimals: 2 } }) // "100.00"
Plus sign / zero-padding
fmt(1, { plus: true }) // "+1" (negatives and zero unaffected)
fmt(5, { pad: 3 }) // "005"
Combining fields
Fields can be freely combined:
fmt(1234.5, { decimals: 2, thousands: true, rounding: 'round', plus: true }) // "+1,234.50"
fmt(12345678, { compact: 'zh', decimals: { max: 2 } }) // "1234.56万"
Try it live
fmt(1234567.891, { decimals: 2, thousands: true }) // "1,234,567.89"
fmt(0.1234, { output: 'percent', decimals: 1 }) // "12.3%"
fmt(1234567, { compact: 'zh', decimals: 2 }) // "123.45万"
calc('150', { _fmt: { clamp: [0, 100] } }) // "100"