packages/intl-numberformat/benchmark/README.md
This benchmark suite measures the performance of @formatjs/intl-numberformat against the native Intl.NumberFormat implementation.
This benchmark was created in response to issue #5023, which reported ~10x performance degradation when formatting numbers repeatedly in React Native applications, particularly for date/time-related values (0-59 for minutes/seconds).
The benchmark includes the following scenarios:
ToRawPrecision (performance hotspot identified in issue)ToRawFixedUsing Bazel:
bazel run //packages/intl-numberformat/benchmark:benchmark
Or using tsx directly from the root:
cd packages/intl-numberformat/benchmark
pnpm install
pnpm exec tsx benchmark.ts
For detailed performance analysis and CPU profiling workflows, see PROFILE.md.
The benchmark uses tinybench and outputs:
Look for significant differences in the "time values 0-59" and "significantDigits" tests, as these relate to the performance issue reported.
Results from running on macOS (Apple Silicon) with Node.js v24.11.1:
┌─────────┬────────────────────────────────────────────┬──────────────────┬───────────────────┬────────────────────────┬────────────────────────┬─────────┐
│ (index) │ Task name │ Latency avg (ns) │ Latency med (ns) │ Throughput avg (ops/s) │ Throughput med (ops/s) │ Samples │
├─────────┼────────────────────────────────────────────┼──────────────────┼───────────────────┼────────────────────────┼────────────────────────┼─────────┤
│ 0 │ 'format decimal (polyfill)' │ '35333 ± 0.36%' │ '34000 ± 500.00' │ '28938 ± 0.10%' │ '29412 ± 439' │ 28303 │
│ 1 │ 'format decimal (native)' │ '1727.4 ± 0.19%' │ '1708.0 ± 42.00' │ '589508 ± 0.02%' │ '585480 ± 14400' │ 578900 │
│ 2 │ 'format percent (polyfill)' │ '34461 ± 1.42%' │ '32583 ± 333.00' │ '30107 ± 0.10%' │ '30691 ± 317' │ 29020 │
│ 3 │ 'format percent (native)' │ '1928.2 ± 0.11%' │ '1916.0 ± 41.00' │ '522869 ± 0.01%' │ '521921 ± 11413' │ 518628 │
│ 4 │ 'format currency (polyfill)' │ '35661 ± 0.27%' │ '34625 ± 375.00' │ '28467 ± 0.09%' │ '28881 ± 316' │ 28043 │
│ 5 │ 'format currency (native)' │ '1924.6 ± 0.13%' │ '1916.0 ± 41.00' │ '524039 ± 0.02%' │ '521921 ± 11413' │ 519580 │
│ 6 │ 'format unit (polyfill)' │ '40681 ± 0.37%' │ '39167 ± 459.00' │ '25083 ± 0.10%' │ '25532 ± 303' │ 24582 │
│ 7 │ 'format with significantDigits (polyfill)' │ '649578 ± 0.36%' │ '632312 ± 6437.0' │ '1546 ± 0.30%' │ '1581 ± 16' │ 1540 │
│ 8 │ 'format with fractionDigits (polyfill)' │ '35427 ± 0.31%' │ '34333 ± 542.00' │ '28760 ± 0.09%' │ '29126 ± 467' │ 28227 │
│ 9 │ 'format time values 0-59 (polyfill)' │ '227776 ± 0.37%' │ '220458 ± 4167.0' │ '4436 ± 0.25%' │ '4536 ± 86' │ 4391 │
│ 10 │ 'format time values 0-59 (native)' │ '10743 ± 0.14%' │ '10584 ± 168.00' │ '93984 ± 0.04%' │ '94482 ± 1524' │ 93084 │
│ 11 │ 'formatToParts decimal (polyfill)' │ '36420 ± 0.62%' │ '34500 ± 625.00' │ '28434 ± 0.12%' │ '28986 ± 516' │ 27458 │
│ 12 │ 'formatToParts decimal (native)' │ '5870.1 ± 0.23%' │ '5750.0 ± 83.00' │ '172230 ± 0.03%' │ '173913 ± 2475' │ 170356 │
└─────────┴────────────────────────────────────────────┴──────────────────┴───────────────────┴────────────────────────┴────────────────────────┴─────────┘
Comparing before/after the fast-path logarithm optimization:
| Benchmark | Before (ops/s) | After (ops/s) | Improvement |
|---|---|---|---|
| format decimal | 2,591 | 28,938 | 11.2x 🚀 |
| format time values 0-59 | 215 | 4,436 | 20.6x 🚀 |
| formatToParts | 2,615 | 28,434 | 10.9x 🚀 |
| format percent | 2,668 | 30,107 | 11.3x |
| format currency | 2,636 | 28,467 | 10.8x |
| format with fractionDigits | 2,603 | 28,760 | 11.0x |
| format unit | 2,554 | 25,083 | 9.8x |
| format with significantDigits | 996 | 1,546 | 1.6x |
Overall speedup: 10-20x faster for most common operations!
Dramatic Performance Improvement:
Native vs Polyfill Gap (After Optimization):
CPU Time Reduction:
Significant Digits Path:
The optimization uses a hybrid fast/slow path approach:
Fast path for simple integers (0-999,999):
Math.log10() instead of Decimal.js logarithmsPower-of-10 caching:
Decimal.pow(10, n) results to avoid repeated calculationsComputeExponent and ToRawFixedMaintains correctness:
This change provides massive performance improvements for the common path while maintaining full correctness.
PartitionNumberPattern.ts - Main entry pointToRawPrecision.ts - Significant digits formatting (hotspot)FormatNumericToString.ts - Core formatting logicformat_to_parts.ts - Part generation logic