Performance

Zero runtime dependencies, ~12KB gzip; precision arithmetic still reaches millions of operations per second.

Performance

@wzo/calc has zero runtime dependencies and the ESM bundle is ~12 KB gzipped. All computation runs internally through BigInt. While it is slower than native floating-point (which is fast but wrong), precision arithmetic still reaches millions of operations per second — more than enough for everyday use.

Comparison with other libraries

Measured throughput against popular precision libraries (ops/s, higher is better; values vary by machine and are meant to indicate order of magnitude):

Single-step operations

Operation@wzo/calcdecimal.jsmathjsa-calc
Addition 0.1 + 0.29,090,000 🥇2,920,0002,040,000431,000
Multiplication 1.1 * 2.28,160,000 🥇2,280,0001,590,000139,000
Division 1 / 32,740,0003,240,000 🥇2,040,000450,000
  • Addition / multiplication: @wzo/calc is fastest, roughly 3–3.6× ahead of decimal.js. These operations use a number integer-scaling fast path — when operands are within the 2^53 safe integer range and have ≤ 15 decimal places, integer arithmetic is used (close to native speed), falling back to BigInt only if overflow is possible. Scaling is done with integer × 10ᵏ, never floating-point multiplication, so there is no precision loss.
  • Division: decimal.js is still fastest (its base-1e7 long division is heavily optimized; also, we compute 50 decimal places while it computes 50 significant digits, a slightly different workload). By borrowing its ideas of pre-stored constants and early termination when the remainder is zero — pre-caching powers of 10 to avoid repeated large-integer construction and a fast path for exact division — we improved from ~1.7M/s to ~2.74M/s, narrowing the gap from 1.8× to 1.18×.

Complex expression evaluation

Real-world workloads rarely involve single operations — they more often involve parentheses, discount/promotion calculations, compound interest with repeated multiplication, or multiple divisions. The expressions below were cross-validated against mathjs (BigNumber 80-digit) and results match exactly; only throughput is compared (decimal.js has no expression parser and is excluded):

Expression@wzo/calcmathjsa-calcdecimal.js
1 + 2 * 2 (simple)2,310,000 🥇590,000419,000— (no expression parser)
Discount (999.99*3 + 499.5*2 + 199.9*5)*0.85 - 200463,000 🥇98,000207,000
Compound interest 100000 * 1.004⁶ - 100000299,000 🥇148,000198,000
Multiple divisions 1000/3 + 2000/7 + 3000/11 + 4000/13 + 5000/17 (repeating decimals)132,00085,000143,000 🥇
  • Expressions dominated by addition/subtraction/multiplication: @wzo/calc is fastest — roughly 5.5× ahead of a-calc and 3.9× ahead of mathjs for simple expressions; roughly 2.2× ahead of a-calc and 4.7× ahead of mathjs for discount calculations.
  • Division-heavy expressions: the advantage narrows to roughly on par with a-calc (results vary), while still ~1.6× ahead of mathjs — high-precision division must go through BigInt and cannot use the number fast path.
  • For single-step addition/subtraction/multiplication, @wzo/calc is 19–20× faster than a-calc, which is also string-based.

The comparison benchmark is at packages/main/tests/compare.bench.ts. Run pnpm --filter @wzo/calc exec vitest bench --run compare to reproduce (decimal.js / mathjs are devDependencies for benchmarking only and are not included in the runtime bundle).

Throughput benchmarks

Results from pnpm --filter @wzo/calc bench (using Vitest Bench); values vary by machine and are meant to indicate order of magnitude:

OperationThroughput (ops/s)
addStr('0.1', '0.2')~8,900,000
mul('1.1', '2.2')~8,100,000
chainAdd(10).sub(3).mul(2)()~7,100,000
add(0.1, 0.2) (→ number)~4,700,000
fmt(n, { decimals: 2, thousands: true })~2,900,000
div('1', '3') (50 places)~2,630,000
calc('1 + 2 * 2')~2,400,000
calc('9.9 * 3', { _fmt }) (evaluate + format)~2,080,000
fmt(n, { compact: 'zh' })~630,000
calc('clamp(max(3, 5) * 2, 0, 100)') (math functions)~630,000
— Reference: native 0.1 + 0.2~50,000,000 (but = 0.30000000000000004)

Native floating-point is roughly 10–15× faster, but produces wrong results. @wzo/calc trades that small speed difference for strict correctness — individual operations still run in microseconds.

Correctness comparison

This is where @wzo/calc delivers its real value — native floating-point fails all of these classic cases:

ExpressionNative JS@wzo/calc
0.1 + 0.20.30000000000000004"0.3"
0.3 - 0.10.19999999999999998"0.2"
0.1 * 0.20.020000000000000004"0.02"
(1.005).toFixed(2)"1.00""1.01" ✓ (with rounding: 'round')
9007199254740993 + 09007199254740992"9007199254740993"
Benchmarks are reproducible: run pnpm --filter @wzo/calc bench from the repository root. Test cases are in packages/main/tests/perf.bench.ts.

Performance tips

  • Pass strings for precision-sensitive values: addStr('0.1', '0.2') is slightly faster than add(0.1, 0.2) and skips the Number() conversion, making it safer too.
  • Fewer formatting fields = faster: compact / clamp and similar options are slightly heavier than plain decimals.