Num — Number Utilities
2.02 kB minified + gzipped
Working with numbers in standard JavaScript or TypeScript often leads to code cluttered with inline
arrow functions, manual Number.isNaN checks, and silent runtime traps:
- Silent
NaNandInfinitypropagation: Parsing an invalid string withNumber("abc")producesNaN, and dividing100 / 0producesInfinity. Both values have the TypeScript typenumber, allowing corrupted math to spread silently into database writes or UI displays. - Unsafe empty collection aggregates: Calling
Math.min(...items)on an empty array evaluates toInfinity,Math.max(...items)evaluates to-Infinity, and computing an average viasum / items.lengthproducesNaNwhenitemsis empty. - Verbose inline math in pipelines: Applying arithmetic, clamping bounds, or formatting
currencies inside a
piperequires repetitive inline functions liken => Math.max(0, Math.min(100, n)).
Num replaces these imperative checks and silent failure modes with declarative, curried,
data-last operations that model failure boundaries — such as unparseable strings, division by
zero, or empty array aggregations — explicitly using Maybe<number>.
Safe Numeric Parsing
Section titled “Safe Numeric Parsing”To convert a string into a number without encountering the NaN trap or accepting blank whitespace
as 0 (which Number(" ") does in standard JavaScript), use Num.from.string. It returns
Some(number) for valid numeric strings and None for empty, whitespace-only, or malformed text:
Curried Arithmetic and Safe Division
Section titled “Curried Arithmetic and Safe Division”Arithmetic functions in Num are curried and place the target number last so they compose directly
inside pipe and Arr.map. In particular, Num.subtract(amount)(from) computes from - amount,
reading naturally in a pipeline as “subtract amount”:
For unary sign adjustments, Num.abs returns the non-negative magnitude of a number and
Num.negate flips its sign:
To prevent division by zero from introducing Infinity or NaN, Num.divide(divisor)(dividend)
and Num.remainder(divisor)(dividend) return Maybe<number>, yielding None whenever divisor is
0:
Rounding
Section titled “Rounding”When converting fractional calculations into whole integers — such as calculating cent amounts, page
counts, or pixel coordinates — Num.round, Num.floor, and Num.ceil provide composable wrappers
around standard rounding rules:
Constraining and Testing Bounds
Section titled “Constraining and Testing Bounds”Use Num.clamp to restrict a number to an inclusive [min, max] interval, Num.between to test
membership in a closed [min, max] interval, and Num.inRange to test membership in a half-open
[start, end) interval (where start is included and end is excluded):
Numeric Predicates (Num.is)
Section titled “Numeric Predicates (Num.is)”The Num.is namespace provides boolean predicates for inspecting numeric properties inside
pipelines and array filters:
Generating Sequences
Section titled “Generating Sequences”To generate an inclusive sequence of numbers without manual for loops,
Num.range(from, to, step?) builds a readonly array stepping by step (default 1). If
from > to, step <= 0, or any argument is non-finite, it safely returns an empty array []:
Safe Collection Aggregations
Section titled “Safe Collection Aggregations”Num provides collection reducers that handle empty arrays safely without returning Infinity or
NaN. Num.sum returns 0 for an empty list, while Num.mean, Num.min, and Num.max return
Maybe<number>:
Locale-Aware Formatting
Section titled “Locale-Aware Formatting”When presenting numbers in a UI or report, Num.format(options?, locales?) wraps
Intl.NumberFormat in a curried, safe function. Because formatting NaN or Infinity into user
interfaces is almost always a bug, Num.format returns Some(formattedString) for finite numbers
and None for NaN, Infinity, or -Infinity:
Pipeline Composition
Section titled “Pipeline Composition”We can combine parsing, filtering, clamping, arithmetic, aggregation, and formatting into a single
pipeline. Here, we process raw discount percentages from a configuration payload, clamp them to the
permitted [0, 50] range, compute the average discount, and format it for display:
Problems it solves
Section titled “Problems it solves”- Sanitizing and clamping untrusted numeric inputs: When extracting pagination offsets, page
limits, or price filter boundaries from query strings, numbers can be malformed or fall outside
permitted domain ranges.
Num.from.stringandNum.clampsanitize and constrain values directly in pipeline workflows. - Safe statistical computations without
InfinityorNaN: Calling standard functions likeMath.min()on an empty collection returnsInfinity, while dividing by an empty list length yieldsNaN.Num.min,Num.max, andNum.meanreturnMaybe<number>, guaranteeing that empty datasets are handled explicitly. - Division-by-zero protection in progress and ratio calculations: Calculating task percentages
(e.g.
completed / total) whentotal === 0producesNaNorInfinityin standard JavaScript, causing broken UI progress bars.Num.divideandNum.remainderguard against division by zero safely. - Safe locale formatting (
Num.format): Formatting numbers into currencies, units, or compact notation viaIntl.NumberFormatwhile automatically rejectingNaNandInfinityvalues asNone. - Linear mathematical pipelines: In shopping cart calculations, rate adjustments, and layout
engines, combining arithmetic and rounding operations (
Num.add,Num.subtract,Num.multiply,Num.abs,Num.negate,Num.round,Num.floor,Num.ceil) insidepipekeeps mathematical transformations readable without temporary variables.