arbitrary-numbers 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +153 -64
  2. package/package.json +2 -2
package/README.md CHANGED
@@ -10,7 +10,15 @@
10
10
  [![Zero dependencies](https://img.shields.io/badge/dependencies-zero-6366f1?labelColor=0c0c0e)](package.json)
11
11
  </div>
12
12
 
13
- `arbitrary-numbers` is a TypeScript library for numbers beyond `Number.MAX_SAFE_INTEGER`. Exact arithmetic at any scale, with fused operations and formula pipelines built for idle game loops and simulations.
13
+ `arbitrary-numbers` fills a specific gap: JavaScript's `Number` type silently loses precision above `Number.MAX_SAFE_INTEGER`, and `BigInt` can't represent decimals, so working with extremely large or small magnitudes often requires manual scaling into very large integers.
14
+
15
+ Numbers are stored as a normalized `coefficient × 10^exponent` pair. That makes arithmetic across wildly different scales fast and predictable — exactly what idle games and simulations need when values span from `1` to `10^300` in the same loop.
16
+
17
+ - **Immutable by default** — every operation returns a new instance, no surprise mutations
18
+ - **Fused operations** (`mulAdd`, `subMul`, ...) — reduce allocations in hot loops
19
+ - **Formula pipelines** — define an expression once, apply it to any number of values
20
+ - **Pluggable display** — swap between scientific, unit (K/M/B/T), and letter notation without touching game logic
21
+ - **Zero dependencies** — nothing to audit, nothing to break
14
22
 
15
23
  ## Install
16
24
 
@@ -25,46 +33,77 @@ Requires TypeScript `"strict": true`.
25
33
  ```typescript
26
34
  import { an, chain, formula, unitNotation } from "arbitrary-numbers";
27
35
 
28
- // Exact at any scale, no overflow or silent precision loss
29
- const base = an(1, 9); // 1,000,000,000
30
- const armor = an(2, 6); // 2,000,000
31
- let gold = an(5, 6); // 5,000,000
36
+ // JavaScript range limits
37
+ const jsHuge = Number("1e500"); // Infinity
38
+ const jsTiny = Number("1e-500"); // 0
32
39
 
33
- // Damage formula: (base - armor) * 0.75
34
- const damage = chain(base)
35
- .subMul(armor, an(7.5, -1))
40
+ // Arbitrary range in both directions
41
+ const huge = an(1, 500);
42
+ const tiny = an(1, -500);
43
+
44
+ // One-off pipeline with chain(): (6.2e15 - 8.5e13) * 0.75
45
+ const damage = chain(an(6.2, 15))
46
+ .subMul(an(8.5, 13), an(7.5, -1))
36
47
  .floor()
37
48
  .done();
38
49
 
39
- damage.toString(unitNotation) // "748.50 M"
50
+ // Reusable per-tick formula: gold = (gold * 1.08) + 2_500_000
51
+ const tick = formula("tick").mulAdd(an(1.08), an(2.5, 6));
40
52
 
41
- // Per-tick update: fused op, one allocation instead of two
42
- gold = gold.mulAdd(an(1.05), an(1, 4)); // (gold * 1.05) + 10,000
53
+ let gold = an(7.5, 12);
54
+ for (let i = 0; i < 3; i += 1) {
55
+ gold = tick.apply(gold);
56
+ }
43
57
 
44
- // Reusable formula pipeline applied to multiple values
45
- const applyArmor = formula("Armor").subMul(armor, an(7.5, -1)).floor();
46
- const physDamage = applyArmor.apply(base);
47
- const magDamage = applyArmor.apply(an(8, 8));
58
+ console.log("=== Range limits (JS vs arbitrary-numbers) ===");
59
+ console.log(`JS Number('1e500') -> ${jsHuge}`);
60
+ console.log(`AN an(1, 500) -> ${huge.toString()}`);
61
+ console.log(`JS Number('1e-500') -> ${jsTiny}`);
62
+ console.log(`AN an(1, -500) -> ${tiny.toString()}`);
48
63
 
49
- // Formatting adapts at every scale automatically
50
- an(1.5, 3).toString(unitNotation) // "1.50 K"
51
- an(1.5, 6).toString(unitNotation) // "1.50 M"
52
- an(1.5, 9).toString(unitNotation) // "1.50 B"
64
+ console.log("");
65
+ console.log("=== Game math helpers ===");
66
+ console.log(`Damage (chain + fused subMul) -> ${damage.toString(unitNotation)}`);
67
+ console.log(`Gold after 3 ticks (formula) -> ${gold.toString(unitNotation)}`);
68
+ ```
69
+
70
+ Example output when running this in a repository checkout (for example with `npx tsx examples/quickstart.ts`):
71
+
72
+ ```text
73
+ === Range limits (JS vs arbitrary-numbers) ===
74
+ JS Number('1e500') -> Infinity
75
+ AN an(1, 500) -> 1.00e+500
76
+ JS Number('1e-500') -> 0
77
+ AN an(1, -500) -> 1.00e-500
78
+
79
+ === Game math helpers ===
80
+ Damage (chain + fused subMul) -> 4.59 Qa
81
+ Gold after 3 ticks (formula) -> 9.45 T
53
82
  ```
54
83
 
55
84
  ## Table of contents
56
85
 
86
+ - [Install](#install)
87
+ - [Quick start](#quick-start)
88
+ - [Table of contents](#table-of-contents)
57
89
  - [Creating numbers](#creating-numbers)
58
90
  - [Arithmetic](#arithmetic)
59
91
  - [Fused operations](#fused-operations)
60
- - [Fluent builder - chain()](#fluent-builder---chain)
61
- - [Reusable formulas - formula()](#reusable-formulas---formula)
92
+ - [Fluent builder - `chain()`](#fluent-builder---chain)
93
+ - [Reusable formulas - `formula()`](#reusable-formulas---formula)
94
+ - [chain() vs formula()](#chain-vs-formula)
62
95
  - [Comparison and predicates](#comparison-and-predicates)
63
96
  - [Rounding and math](#rounding-and-math)
64
97
  - [Display and formatting](#display-and-formatting)
98
+ - [scientificNotation (default)](#scientificnotation-default)
99
+ - [unitNotation - K, M, B, T...](#unitnotation---k-m-b-t)
100
+ - [AlphabetNotation - a, b, c... aa, ab...](#alphabetnotation---a-b-c-aa-ab)
65
101
  - [Precision control](#precision-control)
66
102
  - [Errors](#errors)
67
103
  - [Utilities](#utilities)
104
+ - [ArbitraryNumberOps - mixed `number | ArbitraryNumber` input](#arbitrarynumberops---mixed-number--arbitrarynumber-input)
105
+ - [ArbitraryNumberGuard - type guards](#arbitrarynumberguard---type-guards)
106
+ - [ArbitraryNumberHelpers - game and simulation patterns](#arbitrarynumberhelpers---game-and-simulation-patterns)
68
107
  - [Writing a custom plugin](#writing-a-custom-plugin)
69
108
  - [Idle game example](#idle-game-example)
70
109
  - [Performance](#performance)
@@ -423,67 +462,117 @@ an(3.2, 6).toString(new TierNotation({ separator: " " })) // "3.20 M"
423
462
 
424
463
  ## Idle game example
425
464
 
426
- A self-contained simulation showing `an()`, fused ops, `chain()`, helpers, and `unitNotation` working together.
465
+ A self-contained simulation showing hyper-growth, fused ops, helpers, and where plain JS `number` overflows while `ArbitraryNumber` keeps working.
427
466
 
428
467
  ```typescript
429
468
  import {
430
- ArbitraryNumber, an, chain,
431
- unitNotation, ArbitraryNumberHelpers as helpers,
469
+ an, chain,
470
+ UnitNotation,
471
+ CLASSIC_UNITS,
472
+ letterNotation,
473
+ ArbitraryNumberHelpers as helpers,
432
474
  } from "arbitrary-numbers";
475
+ import type { ArbitraryNumber } from "arbitrary-numbers";
433
476
 
434
- let gold = ArbitraryNumber.Zero;
435
- let goldPerSec = an(1);
477
+ let gold = an(5, 6); // 5,000,000
478
+ let gps = an(2, 5); // 200,000 per tick
479
+ let reactorCost = an(1, 9);
480
+ let reactors = 0;
436
481
 
437
- const UPGRADES = [
438
- { label: "Copper Pick ", cost: an(50), mult: an(5) },
439
- { label: "Iron Mine ", cost: an(1, 3), mult: an(20) },
440
- { label: "Gold Refinery", cost: an(5, 6), mult: an(1, 4) },
441
- ] as const;
482
+ const display = new UnitNotation({
483
+ units: CLASSIC_UNITS,
484
+ fallback: letterNotation,
485
+ });
442
486
 
443
- function tick(): void {
444
- gold = gold.add(goldPerSec);
487
+ function fmt(value: ArbitraryNumber, decimals = 2): string {
488
+ return value.toString(display, decimals);
445
489
  }
446
490
 
447
- function tryBuyAll(): void {
448
- for (const u of UPGRADES) {
449
- if (!helpers.meetsOrExceeds(gold, u.cost)) continue;
450
- gold = gold.sub(u.cost);
451
- goldPerSec = goldPerSec.mul(u.mult);
452
- console.log(` bought ${u.label} GPS: ${goldPerSec.toString(unitNotation)}`);
453
- }
491
+ function snapshot(tick: number): void {
492
+ console.log(
493
+ `[t=${String(tick).padStart(4)}] SNAPSHOT `
494
+ + `gold=${fmt(gold, 2).padStart(12)} gps=${fmt(gps, 2).padStart(12)}`,
495
+ );
454
496
  }
455
497
 
456
- function prestige(multiplier: ArbitraryNumber): void {
457
- goldPerSec = chain(goldPerSec)
458
- .mulAdd(multiplier, an(1)) // (gps * mult) + 1, fused
459
- .floor()
460
- .done();
461
- console.log(` prestige! new GPS: ${goldPerSec.toString(unitNotation)}`);
462
- }
498
+ console.log("=== Hyper-growth idle loop (720 ticks) ===");
499
+ console.log(`start gold=${fmt(gold)} gps=${fmt(gps)} reactorCost=${fmt(reactorCost)}`);
500
+
501
+ for (let t = 1; t <= 720; t += 1) {
502
+ // Core growth: gold = (gold * 1.12) + gps
503
+ gold = gold.mulAdd(an(1.12), gps);
504
+
505
+ if (t % 60 === 0 && helpers.meetsOrExceeds(gold, reactorCost)) {
506
+ const before = gps;
507
+ gold = gold.sub(reactorCost);
508
+ gps = chain(gps).mul(an(1, 25)).floor().done();
509
+ reactorCost = reactorCost.mul(an(8));
510
+ reactors += 1;
511
+
512
+ console.log(
513
+ `[t=${String(t).padStart(4)}] REACTOR #${String(reactors).padStart(2)} `
514
+ + `gps ${fmt(before)} -> ${fmt(gps)} `
515
+ + `nextCost=${fmt(reactorCost)}`,
516
+ );
517
+ }
463
518
 
464
- for (let t = 1; t <= 1_000_000; t++) {
465
- tick();
466
- if (t % 10 === 0) tryBuyAll();
467
- if (t === 51_000) prestige(an(1.5));
468
- if (t % 250_000 === 0) {
469
- const g = gold.toString(unitNotation, 3);
470
- const gps = goldPerSec.toString(unitNotation, 3);
471
- console.log(`[t=${String(t).padStart(9)}] gold: ${g.padStart(12)} gps: ${gps}`);
519
+ if (t === 240 || t === 480) {
520
+ const before = gps;
521
+ gps = chain(gps)
522
+ .mul(an(1, 4))
523
+ .add(an(7.5, 6))
524
+ .floor()
525
+ .done();
526
+ console.log(`[t=${String(t).padStart(4)}] PRESTIGE gps ${fmt(before)} -> ${fmt(gps)}`);
527
+ }
528
+
529
+ if (t % 120 === 0) {
530
+ snapshot(t);
472
531
  }
473
532
  }
533
+
534
+ console.log("\n=== Final scale check ===");
535
+ console.log(`reactors bought: ${reactors}`);
536
+ console.log(`final gold (unit+letter): ${fmt(gold)}`);
537
+ console.log(`final gps (unit+letter): ${fmt(gps)}`);
538
+ console.log(`final gold as JS Number: ${gold.toNumber()}`);
539
+ console.log(`final gps as JS Number : ${gps.toNumber()}`);
540
+ console.log("If JS shows Infinity while unit+letter output stays finite, the library is doing its job.");
474
541
  ```
475
542
 
476
543
  Output:
477
544
 
478
- ```
479
- bought Copper Pick GPS: 5.00
480
- bought Iron Mine GPS: 100.00
481
- bought Gold Refinery GPS: 1.00 M
482
- prestige! new GPS: 1.50 M
483
- [t= 250000] gold: 199.750 B gps: 1.500 M
484
- [t= 500000] gold: 574.750 B gps: 1.500 M
485
- [t= 750000] gold: 949.750 B gps: 1.500 M
486
- [t= 1000000] gold: 1.325 T gps: 1.500 M
545
+ ```text
546
+ === Hyper-growth idle loop (720 ticks) ===
547
+ start gold=5.00 M gps=200.00 K reactorCost=1.00 B
548
+ [t= 60] REACTOR # 1 gps 200.00 K -> 2.00 No nextCost=8.00 B
549
+ [t= 120] REACTOR # 2 gps 2.00 No -> 20.00 SpDc nextCost=64.00 B
550
+ [t= 120] SNAPSHOT gold= 14.94 Dc gps= 20.00 SpDc
551
+ [t= 180] REACTOR # 3 gps 20.00 SpDc -> 200.00 QiVg nextCost=512.00 B
552
+ [t= 240] REACTOR # 4 gps 200.00 QiVg -> 2.00 ai nextCost=4.10 T
553
+ [t= 240] PRESTIGE gps 2.00 ai -> 20.00 aj
554
+ [t= 240] SNAPSHOT gold= 1.49 SpVg gps= 20.00 aj
555
+ [t= 300] REACTOR # 5 gps 20.00 aj -> 200.00 ar nextCost=32.77 T
556
+ [t= 360] REACTOR # 6 gps 200.00 ar -> 2.00 ba nextCost=262.14 T
557
+ [t= 360] SNAPSHOT gold= 1.49 at gps= 2.00 ba
558
+ [t= 420] REACTOR # 7 gps 2.00 ba -> 20.00 bi nextCost=2.10 Qa
559
+ [t= 480] REACTOR # 8 gps 20.00 bi -> 200.00 bq nextCost=16.78 Qa
560
+ [t= 480] PRESTIGE gps 200.00 bq -> 2.00 bs
561
+ [t= 480] SNAPSHOT gold= 149.43 bj gps= 2.00 bs
562
+ [t= 540] REACTOR # 9 gps 2.00 bs -> 20.00 ca nextCost=134.22 Qa
563
+ [t= 600] REACTOR #10 gps 20.00 ca -> 200.00 ci nextCost=1.07 Qi
564
+ [t= 600] SNAPSHOT gold= 149.43 cb gps= 200.00 ci
565
+ [t= 660] REACTOR #11 gps 200.00 ci -> 2.00 cr nextCost=8.59 Qi
566
+ [t= 720] REACTOR #12 gps 2.00 cr -> 20.00 cz nextCost=68.72 Qi
567
+ [t= 720] SNAPSHOT gold= 14.94 cs gps= 20.00 cz
568
+
569
+ === Final scale check ===
570
+ reactors bought: 12
571
+ final gold (unit+letter): 14.94 cs
572
+ final gps (unit+letter): 20.00 cz
573
+ final gold as JS Number: 1.494328222485101e+292
574
+ final gps as JS Number : Infinity
575
+ If JS shows Infinity while unit+letter output stays finite, the library is doing its job.
487
576
  ```
488
577
 
489
578
  ## Performance
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "arbitrary-numbers",
3
- "version": "1.0.0",
4
- "description": "Immutable arbitrary-magnitude numbers for idle games, simulations, and scientific computing. Stored as coefficient × 10^exponent with pluggable display formatting.",
3
+ "version": "1.0.2",
4
+ "description": "Arbitrary-magnitude arithmetic for idle games and simulations, with fused operations, formula pipelines, and pluggable number formatting.",
5
5
  "author": "Chris",
6
6
  "license": "MIT",
7
7
  "repository": {