Performance
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/calc | decimal.js | mathjs | a-calc |
|---|---|---|---|---|
Addition 0.1 + 0.2 | 9,090,000 🥇 | 2,920,000 | 2,040,000 | 431,000 |
Multiplication 1.1 * 2.2 | 8,160,000 🥇 | 2,280,000 | 1,590,000 | 139,000 |
Division 1 / 3 | 2,740,000 | 3,240,000 🥇 | 2,040,000 | 450,000 |
- Addition / multiplication:
@wzo/calcis fastest, roughly 3–3.6× ahead of decimal.js. These operations use a number integer-scaling fast path — when operands are within the2^53safe 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/calc | mathjs | a-calc | decimal.js |
|---|---|---|---|---|
1 + 2 * 2 (simple) | 2,310,000 🥇 | 590,000 | 419,000 | — (no expression parser) |
Discount (999.99*3 + 499.5*2 + 199.9*5)*0.85 - 200 | 463,000 🥇 | 98,000 | 207,000 | — |
Compound interest 100000 * 1.004⁶ - 100000 | 299,000 🥇 | 148,000 | 198,000 | — |
Multiple divisions 1000/3 + 2000/7 + 3000/11 + 4000/13 + 5000/17 (repeating decimals) | 132,000 | 85,000 | 143,000 🥇 | — |
- Expressions dominated by addition/subtraction/multiplication:
@wzo/calcis 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/calcis 19–20× faster than a-calc, which is also string-based.
The comparison benchmark is at
packages/main/tests/compare.bench.ts. Runpnpm --filter @wzo/calc exec vitest bench --run compareto 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:
| Operation | Throughput (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/calctrades 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:
| Expression | Native JS | @wzo/calc |
|---|---|---|
0.1 + 0.2 | 0.30000000000000004 ❌ | "0.3" ✓ |
0.3 - 0.1 | 0.19999999999999998 ❌ | "0.2" ✓ |
0.1 * 0.2 | 0.020000000000000004 ❌ | "0.02" ✓ |
(1.005).toFixed(2) | "1.00" ❌ | "1.01" ✓ (with rounding: 'round') |
9007199254740993 + 0 | 9007199254740992 ❌ | "9007199254740993" ✓ |
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 thanadd(0.1, 0.2)and skips theNumber()conversion, making it safer too. - Fewer formatting fields = faster:
compact/clampand similar options are slightly heavier than plaindecimals.