arbitrary-numbers 1.0.1 → 1.1.0
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.
- package/README.md +311 -77
- package/dist/index.cjs +392 -46
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +262 -25
- package/dist/index.d.ts +262 -25
- package/dist/index.js +392 -46
- package/dist/index.js.map +1 -1
- package/package.json +9 -1
package/README.md
CHANGED
|
@@ -15,11 +15,23 @@
|
|
|
15
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
16
|
|
|
17
17
|
- **Immutable by default** — every operation returns a new instance, no surprise mutations
|
|
18
|
-
- **Fused operations** (`mulAdd`, `subMul`, ...) — reduce allocations in hot loops
|
|
18
|
+
- **Fused operations** (`mulAdd`, `subMul`, `mulDiv`, ...) — reduce allocations in hot loops
|
|
19
19
|
- **Formula pipelines** — define an expression once, apply it to any number of values
|
|
20
20
|
- **Pluggable display** — swap between scientific, unit (K/M/B/T), and letter notation without touching game logic
|
|
21
|
+
- **Save / load built-in** — `toJSON()` / `fromJSON()` / `parse()` for idle-game persistence
|
|
21
22
|
- **Zero dependencies** — nothing to audit, nothing to break
|
|
22
23
|
|
|
24
|
+
## How it compares
|
|
25
|
+
|
|
26
|
+
| Library | Strengths | Limitations | Pick when |
|
|
27
|
+
|---|---|---|---|
|
|
28
|
+
| **arbitrary-numbers** | TypeScript-first, fused ops, `mulDiv`, pluggable notation, serialization, zero deps | Newer; coefficient is always float64 | You want types, ergonomics, and notation flexibility |
|
|
29
|
+
| `break_infinity.js` | Very fast, large incremental-game community, battle-tested | JS only, no types, plugin system is bolt-on | Max speed and community examples matter most |
|
|
30
|
+
| `break_eternity.js` | Handles super-exponent range up to `e(9e15)` | Heavier, more complex API | You genuinely need values beyond `10^(10^15)` |
|
|
31
|
+
| `decimal.js` | Arbitrary *precision* (not just range) | 4–14× slower for game math | Financial math, exact decimal arithmetic |
|
|
32
|
+
|
|
33
|
+
`arbitrary-numbers` stores coefficients as float64, giving ~15 significant digits of precision — the same as a plain JS `number`. If you need exact decimal arithmetic, use `decimal.js`. If you need exponents beyond ~10^(10^15), use `break_eternity`.
|
|
34
|
+
|
|
23
35
|
## Install
|
|
24
36
|
|
|
25
37
|
```sh
|
|
@@ -33,46 +45,70 @@ Requires TypeScript `"strict": true`.
|
|
|
33
45
|
```typescript
|
|
34
46
|
import { an, chain, formula, unitNotation } from "arbitrary-numbers";
|
|
35
47
|
|
|
36
|
-
//
|
|
37
|
-
const
|
|
38
|
-
const
|
|
39
|
-
let gold = an(5, 6); // 5,000,000
|
|
48
|
+
// JavaScript range limits
|
|
49
|
+
const jsHuge = Number("1e500"); // Infinity
|
|
50
|
+
const jsTiny = Number("1e-500"); // 0
|
|
40
51
|
|
|
41
|
-
//
|
|
42
|
-
const
|
|
43
|
-
|
|
52
|
+
// Arbitrary range in both directions
|
|
53
|
+
const huge = an(1, 500);
|
|
54
|
+
const tiny = an(1, -500);
|
|
55
|
+
|
|
56
|
+
// One-off pipeline with chain(): (6.2e15 - 8.5e13) * 0.75
|
|
57
|
+
const damage = chain(an(6.2, 15))
|
|
58
|
+
.subMul(an(8.5, 13), an(7.5, -1))
|
|
44
59
|
.floor()
|
|
45
60
|
.done();
|
|
46
61
|
|
|
47
|
-
|
|
62
|
+
// Reusable per-tick formula: gold = (gold * 1.08) + 2_500_000
|
|
63
|
+
const tick = formula("tick").mulAdd(an(1.08), an(2.5, 6));
|
|
48
64
|
|
|
49
|
-
|
|
50
|
-
|
|
65
|
+
let gold = an(7.5, 12);
|
|
66
|
+
for (let i = 0; i < 3; i += 1) {
|
|
67
|
+
gold = tick.apply(gold);
|
|
68
|
+
}
|
|
51
69
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
70
|
+
console.log("=== Range limits (JS vs arbitrary-numbers) ===");
|
|
71
|
+
console.log(`JS Number('1e500') -> ${jsHuge}`);
|
|
72
|
+
console.log(`AN an(1, 500) -> ${huge.toString()}`);
|
|
73
|
+
console.log(`JS Number('1e-500') -> ${jsTiny}`);
|
|
74
|
+
console.log(`AN an(1, -500) -> ${tiny.toString()}`);
|
|
56
75
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
76
|
+
console.log("");
|
|
77
|
+
console.log("=== Game math helpers ===");
|
|
78
|
+
console.log(`Damage (chain + fused subMul) -> ${damage.toString(unitNotation)}`);
|
|
79
|
+
console.log(`Gold after 3 ticks (formula) -> ${gold.toString(unitNotation)}`);
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Example output when running this in a repository checkout (for example with `npx tsx examples/quickstart.ts`):
|
|
83
|
+
|
|
84
|
+
```text
|
|
85
|
+
=== Range limits (JS vs arbitrary-numbers) ===
|
|
86
|
+
JS Number('1e500') -> Infinity
|
|
87
|
+
AN an(1, 500) -> 1.00e+500
|
|
88
|
+
JS Number('1e-500') -> 0
|
|
89
|
+
AN an(1, -500) -> 1.00e-500
|
|
90
|
+
|
|
91
|
+
=== Game math helpers ===
|
|
92
|
+
Damage (chain + fused subMul) -> 4.59 Qa
|
|
93
|
+
Gold after 3 ticks (formula) -> 9.45 T
|
|
61
94
|
```
|
|
62
95
|
|
|
63
96
|
## Table of contents
|
|
64
97
|
|
|
98
|
+
- [How it compares](#how-it-compares)
|
|
65
99
|
- [Install](#install)
|
|
66
100
|
- [Quick start](#quick-start)
|
|
67
101
|
- [Table of contents](#table-of-contents)
|
|
68
102
|
- [Creating numbers](#creating-numbers)
|
|
69
103
|
- [Arithmetic](#arithmetic)
|
|
104
|
+
- [Negative numbers](#negative-numbers)
|
|
70
105
|
- [Fused operations](#fused-operations)
|
|
71
106
|
- [Fluent builder - `chain()`](#fluent-builder---chain)
|
|
72
107
|
- [Reusable formulas - `formula()`](#reusable-formulas---formula)
|
|
73
108
|
- [chain() vs formula()](#chain-vs-formula)
|
|
74
109
|
- [Comparison and predicates](#comparison-and-predicates)
|
|
75
110
|
- [Rounding and math](#rounding-and-math)
|
|
111
|
+
- [Serialization and save-load](#serialization-and-save-load)
|
|
76
112
|
- [Display and formatting](#display-and-formatting)
|
|
77
113
|
- [scientificNotation (default)](#scientificnotation-default)
|
|
78
114
|
- [unitNotation - K, M, B, T...](#unitnotation---k-m-b-t)
|
|
@@ -136,6 +172,30 @@ a.negate() // -3,000,000
|
|
|
136
172
|
a.abs() // 3,000,000
|
|
137
173
|
```
|
|
138
174
|
|
|
175
|
+
## Negative numbers
|
|
176
|
+
|
|
177
|
+
All operations support negative coefficients. The sign is carried in the coefficient — the exponent is always the magnitude.
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
const debt = an(-5, 6); // -5,000,000
|
|
181
|
+
const income = an(2, 6); // 2,000,000
|
|
182
|
+
|
|
183
|
+
debt.add(income) // -3,000,000
|
|
184
|
+
debt.abs() // 5,000,000
|
|
185
|
+
debt.negate() // 5,000,000
|
|
186
|
+
debt.sign() // -1
|
|
187
|
+
debt.isNegative() // true
|
|
188
|
+
|
|
189
|
+
// Notation plugins preserve the sign
|
|
190
|
+
import { unitNotation, letterNotation, scientificNotation } from "arbitrary-numbers";
|
|
191
|
+
|
|
192
|
+
an(-1.5, 6).toString(scientificNotation) // "-1.50e+6"
|
|
193
|
+
an(-1.5, 6).toString(unitNotation) // "-1.50 M"
|
|
194
|
+
an(-1.5, 6).toString(letterNotation) // "-1.50b"
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Negative numbers are less common in idle games (resources can't go below zero), but they are useful for delta-income tracking, balance sheets, and damage-over-time effects. All arithmetic, comparison, rounding, and formatting methods handle negatives correctly across the full exponent range.
|
|
198
|
+
|
|
139
199
|
## Fused operations
|
|
140
200
|
|
|
141
201
|
Fused methods compute a two-step expression in one normalisation pass, saving one intermediate allocation per call. Use them in per-tick update loops.
|
|
@@ -145,15 +205,21 @@ Fused methods compute a two-step expression in one normalisation pass, saving on
|
|
|
145
205
|
gold = gold.mulAdd(prestigeRate, prestigeBonus);
|
|
146
206
|
|
|
147
207
|
// Other fused pairs
|
|
148
|
-
base.addMul(bonus, multiplier);
|
|
149
|
-
income.mulSub(rate, upkeep);
|
|
150
|
-
raw.subMul(reduction, boost);
|
|
151
|
-
damage.divAdd(speed, flat);
|
|
208
|
+
base.addMul(bonus, multiplier); // (base + bonus) * multiplier
|
|
209
|
+
income.mulSub(rate, upkeep); // (income * rate) - upkeep
|
|
210
|
+
raw.subMul(reduction, boost); // (raw - reduction) * boost
|
|
211
|
+
damage.divAdd(speed, flat); // (damage / speed) + flat
|
|
212
|
+
production.mulDiv(deltaTime, cost); // (production * deltaTime) / cost
|
|
152
213
|
|
|
153
214
|
// Sum an array in one pass, ~9x faster than .reduce((a, b) => a.add(b))
|
|
154
215
|
const total = ArbitraryNumber.sumArray(incomeSources);
|
|
216
|
+
|
|
217
|
+
// Multiply all elements in one pass
|
|
218
|
+
const product = ArbitraryNumber.productArray(multipliers);
|
|
155
219
|
```
|
|
156
220
|
|
|
221
|
+
`mulDiv` is the idle-tick workhorse: `(production * deltaTime) / cost` without allocating an intermediate value for the product.
|
|
222
|
+
|
|
157
223
|
## Fluent builder - `chain()`
|
|
158
224
|
|
|
159
225
|
`chain()` wraps an `ArbitraryNumber` in a thin accumulator. Each method mutates the accumulated value and returns `this`. No expression tree, no deferred execution.
|
|
@@ -246,22 +312,112 @@ ArbitraryNumber.lerp(a, b, 0.5) // 9,500
|
|
|
246
312
|
```typescript
|
|
247
313
|
const n = an(1.75, 0); // 1.75
|
|
248
314
|
|
|
249
|
-
n.floor() //
|
|
250
|
-
n.ceil() //
|
|
251
|
-
n.round() //
|
|
315
|
+
n.floor() // 1 (toward -∞)
|
|
316
|
+
n.ceil() // 2 (toward +∞)
|
|
317
|
+
n.round() // 2 (half-up)
|
|
318
|
+
n.trunc() // 1 (toward 0)
|
|
319
|
+
|
|
320
|
+
an(-1.75, 0).floor() // -2 (toward -∞)
|
|
321
|
+
an(-1.75, 0).trunc() // -1 (toward 0, unlike floor)
|
|
252
322
|
|
|
253
323
|
an(4, 0).sqrt() // 2 (1.18x faster than .pow(0.5))
|
|
254
324
|
an(1, 4).sqrt() // 100
|
|
255
325
|
an(-4, 0).sqrt() // throws ArbitraryNumberDomainError
|
|
256
326
|
|
|
327
|
+
an(8, 0).cbrt() // 2
|
|
328
|
+
an(1, 9).cbrt() // 1e3 (= 1,000)
|
|
329
|
+
an(-27, 0).cbrt() // -3 (cube root supports negatives)
|
|
330
|
+
|
|
257
331
|
an(1, 3).log10() // 3
|
|
258
332
|
an(1.5, 3).log10() // 3.176...
|
|
333
|
+
an(1024, 0).log(2) // 10
|
|
334
|
+
an(Math.E, 0).ln() // ≈ 1
|
|
335
|
+
|
|
336
|
+
ArbitraryNumber.exp10(6) // 1e6 (inverse of log10)
|
|
337
|
+
ArbitraryNumber.exp10(3.5) // ≈ 3162.3
|
|
338
|
+
|
|
259
339
|
ArbitraryNumber.Zero.log10() // throws ArbitraryNumberDomainError
|
|
260
340
|
|
|
261
341
|
an(1.5, 3).toNumber() // 1500
|
|
262
342
|
an(1, 400).toNumber() // Infinity (exponent beyond float64 range)
|
|
343
|
+
|
|
344
|
+
// Batch operations
|
|
345
|
+
ArbitraryNumber.productArray([an(2), an(3), an(4)]) // 24
|
|
346
|
+
ArbitraryNumber.maxOfArray([an(1), an(3), an(2)]) // an(3)
|
|
347
|
+
ArbitraryNumber.minOfArray([an(3), an(1), an(2)]) // an(1)
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
## Serialization and save-load
|
|
351
|
+
|
|
352
|
+
Idle games need to persist numbers across sessions. `arbitrary-numbers` provides three serialization paths:
|
|
353
|
+
|
|
354
|
+
### JSON (recommended for save files)
|
|
355
|
+
|
|
356
|
+
```typescript
|
|
357
|
+
import { ArbitraryNumber, an } from "arbitrary-numbers";
|
|
358
|
+
|
|
359
|
+
const gold = an(1.5, 6);
|
|
360
|
+
|
|
361
|
+
// Serialize — produces { c: number, e: number }
|
|
362
|
+
const blob = gold.toJSON(); // { c: 1.5, e: 6 }
|
|
363
|
+
const json = JSON.stringify(gold); // '{"c":1.5,"e":6}'
|
|
364
|
+
|
|
365
|
+
// Deserialize
|
|
366
|
+
const restored = ArbitraryNumber.fromJSON(JSON.parse(json));
|
|
367
|
+
restored.equals(gold); // true
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
`toJSON()` uses short keys (`c`/`e`) to keep save blobs small. The shape is stable — renaming internal fields will never silently break your saves.
|
|
371
|
+
|
|
372
|
+
### Compact string (URL params, cookies)
|
|
373
|
+
|
|
374
|
+
```typescript
|
|
375
|
+
const raw = gold.toRaw(); // "1.5|6"
|
|
376
|
+
const restored = ArbitraryNumber.parse("1.5|6");
|
|
377
|
+
restored.equals(gold); // true
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
### Parsing arbitrary strings
|
|
381
|
+
|
|
382
|
+
`ArbitraryNumber.parse()` accepts multiple formats:
|
|
383
|
+
|
|
384
|
+
```typescript
|
|
385
|
+
ArbitraryNumber.parse("1.5|6") // pipe format (exact round-trip)
|
|
386
|
+
ArbitraryNumber.parse("1.5e+6") // scientific notation
|
|
387
|
+
ArbitraryNumber.parse("1500000") // plain decimal
|
|
388
|
+
ArbitraryNumber.parse("-0.003") // negative decimal
|
|
389
|
+
ArbitraryNumber.parse("1.5E6") // uppercase E
|
|
263
390
|
```
|
|
264
391
|
|
|
392
|
+
Unrecognised or non-finite strings throw `ArbitraryNumberInputError`.
|
|
393
|
+
|
|
394
|
+
### Save-every-60s pattern
|
|
395
|
+
|
|
396
|
+
```typescript
|
|
397
|
+
// Save
|
|
398
|
+
function saveGame(state: GameState): string {
|
|
399
|
+
return JSON.stringify({
|
|
400
|
+
gold: state.gold.toJSON(),
|
|
401
|
+
gps: state.gps.toJSON(),
|
|
402
|
+
tick: state.tick,
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Load
|
|
407
|
+
function loadGame(json: string): GameState {
|
|
408
|
+
const raw = JSON.parse(json);
|
|
409
|
+
return {
|
|
410
|
+
gold: ArbitraryNumber.fromJSON(raw.gold),
|
|
411
|
+
gps: ArbitraryNumber.fromJSON(raw.gps),
|
|
412
|
+
tick: raw.tick,
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
setInterval(() => localStorage.setItem("save", saveGame(state)), 60_000);
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
> **Precision note:** `toJSON()` / `fromJSON()` round-trip is exact. `toRaw()` / `parse()` with the pipe format is also exact. Parsing a number from a notation string (e.g. `"1.50 M"`) is not provided — it would be lossy because the suffix format discards precision beyond `decimals`.
|
|
420
|
+
|
|
265
421
|
## Display and formatting
|
|
266
422
|
|
|
267
423
|
`toString(plugin?, decimals?)` accepts any `NotationPlugin`. Three plugins are included.
|
|
@@ -330,7 +486,7 @@ When two numbers differ in exponent by more than `PrecisionCutoff` (default `15`
|
|
|
330
486
|
const huge = an(1, 20); // 10^20
|
|
331
487
|
const tiny = an(1, 3); // 1,000
|
|
332
488
|
|
|
333
|
-
huge.add(tiny) // returns huge unchanged
|
|
489
|
+
huge.add(tiny) // returns huge unchanged — tiny is negligible at 10^20 scale
|
|
334
490
|
```
|
|
335
491
|
|
|
336
492
|
Override globally or for a single scoped block:
|
|
@@ -342,6 +498,34 @@ ArbitraryNumber.PrecisionCutoff = 50; // global
|
|
|
342
498
|
const result = ArbitraryNumber.withPrecision(50, () => a.add(b));
|
|
343
499
|
```
|
|
344
500
|
|
|
501
|
+
### When things drift — precision troubleshooting
|
|
502
|
+
|
|
503
|
+
**"Why doesn't `a.add(b).sub(b).equals(a)` return true when `b` is much larger than `a`?"**
|
|
504
|
+
|
|
505
|
+
```typescript
|
|
506
|
+
const a = an(1, 3); // 1,000
|
|
507
|
+
const b = an(1, 20); // 10^20
|
|
508
|
+
|
|
509
|
+
a.add(b) // returns b — a is negligible, discarded
|
|
510
|
+
a.add(b).sub(b) // returns b.sub(b) = 0, not a
|
|
511
|
+
a.add(b).sub(b).equals(a) // false — a was lost when added to b
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
This is the fundamental float64 limitation: `1,000 + 10^20 = 10^20` in any number system with ~15 significant digits. `arbitrary-numbers` does not hide this — it is exact for the scale at which each value is stored.
|
|
515
|
+
|
|
516
|
+
**Game patterns and their typical precision needs:**
|
|
517
|
+
|
|
518
|
+
| Pattern | Exponent diff | Precision loss |
|
|
519
|
+
|---|---|---|
|
|
520
|
+
| Same-tier resources (gold + gold) | 0–3 | None |
|
|
521
|
+
| Upgrade costs vs balance | 0–8 | None |
|
|
522
|
+
| Prestige multipliers | 15–25 | < 0.0001% |
|
|
523
|
+
| Idle accumulation over time | 20–50 | ~0.1% (acceptable for display) |
|
|
524
|
+
|
|
525
|
+
If exact addition across large exponent gaps matters (e.g. financial ledger), raise `PrecisionCutoff` to `50` and use `withPrecision`.
|
|
526
|
+
|
|
527
|
+
> **Warning:** `PrecisionCutoff` is a global static. Changing it affects all arithmetic library-wide, including in async callbacks that run concurrently. Use `withPrecision` for scoped overrides.
|
|
528
|
+
|
|
345
529
|
## Errors
|
|
346
530
|
|
|
347
531
|
All errors thrown by the library extend `ArbitraryNumberError`, so you can distinguish them from your own errors.
|
|
@@ -441,67 +625,117 @@ an(3.2, 6).toString(new TierNotation({ separator: " " })) // "3.20 M"
|
|
|
441
625
|
|
|
442
626
|
## Idle game example
|
|
443
627
|
|
|
444
|
-
A self-contained simulation showing
|
|
628
|
+
A self-contained simulation showing hyper-growth, fused ops, helpers, and where plain JS `number` overflows while `ArbitraryNumber` keeps working.
|
|
445
629
|
|
|
446
630
|
```typescript
|
|
447
631
|
import {
|
|
448
|
-
|
|
449
|
-
|
|
632
|
+
an, chain,
|
|
633
|
+
UnitNotation,
|
|
634
|
+
CLASSIC_UNITS,
|
|
635
|
+
letterNotation,
|
|
636
|
+
ArbitraryNumberHelpers as helpers,
|
|
450
637
|
} from "arbitrary-numbers";
|
|
638
|
+
import type { ArbitraryNumber } from "arbitrary-numbers";
|
|
451
639
|
|
|
452
|
-
let gold
|
|
453
|
-
let
|
|
640
|
+
let gold = an(5, 6); // 5,000,000
|
|
641
|
+
let gps = an(2, 5); // 200,000 per tick
|
|
642
|
+
let reactorCost = an(1, 9);
|
|
643
|
+
let reactors = 0;
|
|
454
644
|
|
|
455
|
-
const
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
] as const;
|
|
645
|
+
const display = new UnitNotation({
|
|
646
|
+
units: CLASSIC_UNITS,
|
|
647
|
+
fallback: letterNotation,
|
|
648
|
+
});
|
|
460
649
|
|
|
461
|
-
function
|
|
462
|
-
|
|
650
|
+
function fmt(value: ArbitraryNumber, decimals = 2): string {
|
|
651
|
+
return value.toString(display, decimals);
|
|
463
652
|
}
|
|
464
653
|
|
|
465
|
-
function
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
gold
|
|
469
|
-
|
|
470
|
-
console.log(` bought ${u.label} GPS: ${goldPerSec.toString(unitNotation)}`);
|
|
471
|
-
}
|
|
654
|
+
function snapshot(tick: number): void {
|
|
655
|
+
console.log(
|
|
656
|
+
`[t=${String(tick).padStart(4)}] SNAPSHOT `
|
|
657
|
+
+ `gold=${fmt(gold, 2).padStart(12)} gps=${fmt(gps, 2).padStart(12)}`,
|
|
658
|
+
);
|
|
472
659
|
}
|
|
473
660
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
661
|
+
console.log("=== Hyper-growth idle loop (720 ticks) ===");
|
|
662
|
+
console.log(`start gold=${fmt(gold)} gps=${fmt(gps)} reactorCost=${fmt(reactorCost)}`);
|
|
663
|
+
|
|
664
|
+
for (let t = 1; t <= 720; t += 1) {
|
|
665
|
+
// Core growth: gold = (gold * 1.12) + gps
|
|
666
|
+
gold = gold.mulAdd(an(1.12), gps);
|
|
667
|
+
|
|
668
|
+
if (t % 60 === 0 && helpers.meetsOrExceeds(gold, reactorCost)) {
|
|
669
|
+
const before = gps;
|
|
670
|
+
gold = gold.sub(reactorCost);
|
|
671
|
+
gps = chain(gps).mul(an(1, 25)).floor().done();
|
|
672
|
+
reactorCost = reactorCost.mul(an(8));
|
|
673
|
+
reactors += 1;
|
|
674
|
+
|
|
675
|
+
console.log(
|
|
676
|
+
`[t=${String(t).padStart(4)}] REACTOR #${String(reactors).padStart(2)} `
|
|
677
|
+
+ `gps ${fmt(before)} -> ${fmt(gps)} `
|
|
678
|
+
+ `nextCost=${fmt(reactorCost)}`,
|
|
679
|
+
);
|
|
680
|
+
}
|
|
481
681
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
console.log(`[t=${String(t).padStart(
|
|
682
|
+
if (t === 240 || t === 480) {
|
|
683
|
+
const before = gps;
|
|
684
|
+
gps = chain(gps)
|
|
685
|
+
.mul(an(1, 4))
|
|
686
|
+
.add(an(7.5, 6))
|
|
687
|
+
.floor()
|
|
688
|
+
.done();
|
|
689
|
+
console.log(`[t=${String(t).padStart(4)}] PRESTIGE gps ${fmt(before)} -> ${fmt(gps)}`);
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
if (t % 120 === 0) {
|
|
693
|
+
snapshot(t);
|
|
490
694
|
}
|
|
491
695
|
}
|
|
696
|
+
|
|
697
|
+
console.log("\n=== Final scale check ===");
|
|
698
|
+
console.log(`reactors bought: ${reactors}`);
|
|
699
|
+
console.log(`final gold (unit+letter): ${fmt(gold)}`);
|
|
700
|
+
console.log(`final gps (unit+letter): ${fmt(gps)}`);
|
|
701
|
+
console.log(`final gold as JS Number: ${gold.toNumber()}`);
|
|
702
|
+
console.log(`final gps as JS Number : ${gps.toNumber()}`);
|
|
703
|
+
console.log("If JS shows Infinity while unit+letter output stays finite, the library is doing its job.");
|
|
492
704
|
```
|
|
493
705
|
|
|
494
706
|
Output:
|
|
495
707
|
|
|
496
|
-
```
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
[t=
|
|
502
|
-
[t=
|
|
503
|
-
[t=
|
|
504
|
-
[t=
|
|
708
|
+
```text
|
|
709
|
+
=== Hyper-growth idle loop (720 ticks) ===
|
|
710
|
+
start gold=5.00 M gps=200.00 K reactorCost=1.00 B
|
|
711
|
+
[t= 60] REACTOR # 1 gps 200.00 K -> 2.00 No nextCost=8.00 B
|
|
712
|
+
[t= 120] REACTOR # 2 gps 2.00 No -> 20.00 SpDc nextCost=64.00 B
|
|
713
|
+
[t= 120] SNAPSHOT gold= 14.94 Dc gps= 20.00 SpDc
|
|
714
|
+
[t= 180] REACTOR # 3 gps 20.00 SpDc -> 200.00 QiVg nextCost=512.00 B
|
|
715
|
+
[t= 240] REACTOR # 4 gps 200.00 QiVg -> 2.00 ai nextCost=4.10 T
|
|
716
|
+
[t= 240] PRESTIGE gps 2.00 ai -> 20.00 aj
|
|
717
|
+
[t= 240] SNAPSHOT gold= 1.49 SpVg gps= 20.00 aj
|
|
718
|
+
[t= 300] REACTOR # 5 gps 20.00 aj -> 200.00 ar nextCost=32.77 T
|
|
719
|
+
[t= 360] REACTOR # 6 gps 200.00 ar -> 2.00 ba nextCost=262.14 T
|
|
720
|
+
[t= 360] SNAPSHOT gold= 1.49 at gps= 2.00 ba
|
|
721
|
+
[t= 420] REACTOR # 7 gps 2.00 ba -> 20.00 bi nextCost=2.10 Qa
|
|
722
|
+
[t= 480] REACTOR # 8 gps 20.00 bi -> 200.00 bq nextCost=16.78 Qa
|
|
723
|
+
[t= 480] PRESTIGE gps 200.00 bq -> 2.00 bs
|
|
724
|
+
[t= 480] SNAPSHOT gold= 149.43 bj gps= 2.00 bs
|
|
725
|
+
[t= 540] REACTOR # 9 gps 2.00 bs -> 20.00 ca nextCost=134.22 Qa
|
|
726
|
+
[t= 600] REACTOR #10 gps 20.00 ca -> 200.00 ci nextCost=1.07 Qi
|
|
727
|
+
[t= 600] SNAPSHOT gold= 149.43 cb gps= 200.00 ci
|
|
728
|
+
[t= 660] REACTOR #11 gps 200.00 ci -> 2.00 cr nextCost=8.59 Qi
|
|
729
|
+
[t= 720] REACTOR #12 gps 2.00 cr -> 20.00 cz nextCost=68.72 Qi
|
|
730
|
+
[t= 720] SNAPSHOT gold= 14.94 cs gps= 20.00 cz
|
|
731
|
+
|
|
732
|
+
=== Final scale check ===
|
|
733
|
+
reactors bought: 12
|
|
734
|
+
final gold (unit+letter): 14.94 cs
|
|
735
|
+
final gps (unit+letter): 20.00 cz
|
|
736
|
+
final gold as JS Number: 1.494328222485101e+292
|
|
737
|
+
final gps as JS Number : Infinity
|
|
738
|
+
If JS shows Infinity while unit+letter output stays finite, the library is doing its job.
|
|
505
739
|
```
|
|
506
740
|
|
|
507
741
|
## Performance
|
|
@@ -512,10 +746,10 @@ Quick reference (Node 22.16, Intel i5-13600KF):
|
|
|
512
746
|
|
|
513
747
|
| Operation | Time |
|
|
514
748
|
|---|---|
|
|
515
|
-
| `add` / `sub` (typical) | ~
|
|
516
|
-
| `mul` / `div` | ~
|
|
517
|
-
| Fused ops (`mulAdd`, `mulSub`, ...) | ~
|
|
518
|
-
| `sumArray(50 items)` | ~
|
|
519
|
-
| `compareTo` (same exponent) | ~
|
|
520
|
-
| `sqrt()` | ~
|
|
521
|
-
| `pow(0.5)` | ~
|
|
749
|
+
| `add` / `sub` (typical) | ~270-275 ns |
|
|
750
|
+
| `mul` / `div` | ~250-255 ns |
|
|
751
|
+
| Fused ops (`mulAdd`, `mulSub`, ...) | ~275 ns, ~1.9x faster than chained |
|
|
752
|
+
| `sumArray(50 items)` | ~435 ns, ~31x faster than `.reduce` |
|
|
753
|
+
| `compareTo` (same exponent) | ~3 ns |
|
|
754
|
+
| `sqrt()` | ~252 ns |
|
|
755
|
+
| `pow(0.5)` | ~20 ns |
|