arbitrary-numbers 1.0.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/dist/index.js ADDED
@@ -0,0 +1,1484 @@
1
+ // src/plugin/ScientificNotation.ts
2
+ var ScientificNotation = class {
3
+ /**
4
+ * Formats a normalised value as `"<coefficient>e±<exponent>"`.
5
+ *
6
+ * @param coefficient - The significand in `[1, 10)` or `0`.
7
+ * @param exponent - The power of 10.
8
+ * @param decimals - Number of decimal places in the output.
9
+ * @returns The formatted string.
10
+ */
11
+ format(coefficient, exponent, decimals) {
12
+ if (exponent === 0) return coefficient.toFixed(decimals);
13
+ const sign = exponent < 0 ? "-" : "+";
14
+ return `${coefficient.toFixed(decimals)}e${sign}${Math.abs(exponent)}`;
15
+ }
16
+ };
17
+ var scientificNotation = new ScientificNotation();
18
+
19
+ // src/constants/pow10.ts
20
+ var POW10 = [
21
+ 1,
22
+ 10,
23
+ 100,
24
+ 1e3,
25
+ 1e4,
26
+ 1e5,
27
+ 1e6,
28
+ 1e7,
29
+ 1e8,
30
+ 1e9,
31
+ 1e10,
32
+ 1e11,
33
+ 1e12,
34
+ 1e13,
35
+ 1e14,
36
+ 1e15
37
+ ];
38
+ function pow10(n) {
39
+ return n >= 0 && n < 16 ? POW10[n] : Math.pow(10, n);
40
+ }
41
+
42
+ // src/errors.ts
43
+ var ArbitraryNumberError = class extends Error {
44
+ constructor(message) {
45
+ super(message);
46
+ this.name = "ArbitraryNumberError";
47
+ }
48
+ };
49
+ var ArbitraryNumberInputError = class extends ArbitraryNumberError {
50
+ constructor(message, value) {
51
+ super(message);
52
+ this.name = "ArbitraryNumberInputError";
53
+ this.value = value;
54
+ }
55
+ };
56
+ var ArbitraryNumberDomainError = class extends ArbitraryNumberError {
57
+ constructor(message, context) {
58
+ super(message);
59
+ this.name = "ArbitraryNumberDomainError";
60
+ this.context = context;
61
+ }
62
+ };
63
+
64
+ // src/core/ArbitraryNumber.ts
65
+ var _ArbitraryNumber = class _ArbitraryNumber {
66
+ /**
67
+ * Creates an `ArbitraryNumber` from a plain JavaScript `number`.
68
+ *
69
+ * Prefer this over `new ArbitraryNumber(value, 0)` when working with
70
+ * ordinary numeric literals - it reads clearly at the call site and
71
+ * validates the input.
72
+ *
73
+ * @example
74
+ * ArbitraryNumber.from(1500); // { coefficient: 1.5, exponent: 3 }
75
+ * ArbitraryNumber.from(0.005); // { coefficient: 5, exponent: -3 }
76
+ * ArbitraryNumber.from(0); // ArbitraryNumber.Zero
77
+ *
78
+ * @param value - Any finite number.
79
+ * @throws `"ArbitraryNumber.from: value must be finite"` for `NaN`, `Infinity`, or `-Infinity`.
80
+ */
81
+ static from(value) {
82
+ if (!isFinite(value)) {
83
+ throw new ArbitraryNumberInputError("ArbitraryNumber.from: value must be finite", value);
84
+ }
85
+ return new _ArbitraryNumber(value, 0);
86
+ }
87
+ /**
88
+ * Constructs a new `ArbitraryNumber` and immediately normalises it so that
89
+ * `1 <= |coefficient| < 10` (or `coefficient === 0`).
90
+ *
91
+ * @example
92
+ * new ArbitraryNumber(15, 3); // stored as { coefficient: 1.5, exponent: 4 }
93
+ *
94
+ * @param coefficient - The significand. Must be a finite number; will be normalised.
95
+ * @param exponent - The power of 10. Must be a finite number.
96
+ * @throws `"ArbitraryNumber: coefficient must be finite"` for `NaN`, `Infinity`, or `-Infinity`.
97
+ * @throws `"ArbitraryNumber: exponent must be finite"` for non-finite exponents.
98
+ */
99
+ constructor(coefficient, exponent) {
100
+ if (coefficient === 0) {
101
+ this.coefficient = 0;
102
+ this.exponent = 0;
103
+ return;
104
+ }
105
+ if (!isFinite(coefficient)) {
106
+ throw new ArbitraryNumberInputError("ArbitraryNumber: coefficient must be finite", coefficient);
107
+ }
108
+ if (!isFinite(exponent)) {
109
+ throw new ArbitraryNumberInputError("ArbitraryNumber: exponent must be finite", exponent);
110
+ }
111
+ const abs = Math.abs(coefficient);
112
+ const shift = Math.floor(Math.log10(abs));
113
+ const scale = pow10(shift);
114
+ if (scale === 0) {
115
+ this.coefficient = 0;
116
+ this.exponent = 0;
117
+ return;
118
+ }
119
+ this.coefficient = coefficient / scale;
120
+ this.exponent = exponent + shift;
121
+ }
122
+ /**
123
+ * @internal Fast-path factory for already-normalised values.
124
+ *
125
+ * Uses Object.create() to bypass the constructor (zero normalisation cost).
126
+ * Only valid when |coefficient| is already in [1, 10) and exponent is correct.
127
+ * Do NOT use for unnormalised inputs - call new ArbitraryNumber(c, e) instead.
128
+ */
129
+ static createNormalized(coefficient, exponent) {
130
+ const n = Object.create(_ArbitraryNumber.prototype);
131
+ n.coefficient = coefficient;
132
+ n.exponent = exponent;
133
+ return n;
134
+ }
135
+ /**
136
+ * Normalises raw (coefficient, exponent) into a new ArbitraryNumber.
137
+ *
138
+ * INVARIANT: all ArbitraryNumber values must have coefficient in [1, 10).
139
+ * Algorithm: shift = floor(log10(|c|)); scale c by 10^-shift; adjust exponent.
140
+ * Cost: one Math.log10 call (~3-4 ns). This is the fundamental cost floor - logarithm
141
+ * is the only way to compute magnitude in JavaScript.
142
+ */
143
+ static normalizeFrom(c, e) {
144
+ if (c === 0) return _ArbitraryNumber.Zero;
145
+ const abs = Math.abs(c);
146
+ const shift = Math.floor(Math.log10(abs));
147
+ const scale = pow10(shift);
148
+ if (scale === 0) return _ArbitraryNumber.Zero;
149
+ return _ArbitraryNumber.createNormalized(c / scale, e + shift);
150
+ }
151
+ /**
152
+ * Returns `this + other`.
153
+ *
154
+ * When the exponent difference exceeds {@link PrecisionCutoff}, the smaller
155
+ * operand has no effect and the larger is returned as-is.
156
+ *
157
+ * @example
158
+ * new ArbitraryNumber(1.5, 3).add(new ArbitraryNumber(2.5, 3)); // 4*10^3
159
+ */
160
+ add(other) {
161
+ if (this.coefficient === 0) return other;
162
+ if (other.coefficient === 0) return this;
163
+ const diff = this.exponent - other.exponent;
164
+ if (diff > _ArbitraryNumber.PrecisionCutoff) return this;
165
+ if (diff < -_ArbitraryNumber.PrecisionCutoff) return other;
166
+ let c;
167
+ let e;
168
+ if (diff >= 0) {
169
+ c = this.coefficient + other.coefficient / pow10(diff);
170
+ e = this.exponent;
171
+ } else {
172
+ c = other.coefficient + this.coefficient / pow10(-diff);
173
+ e = other.exponent;
174
+ }
175
+ return _ArbitraryNumber.normalizeFrom(c, e);
176
+ }
177
+ /**
178
+ * Returns `this - other`.
179
+ *
180
+ * @example
181
+ * new ArbitraryNumber(3.5, 3).sub(new ArbitraryNumber(1.5, 3)); // 2*10^3
182
+ */
183
+ sub(other) {
184
+ if (other.coefficient === 0) return this;
185
+ const negC = -other.coefficient;
186
+ if (this.coefficient === 0) return _ArbitraryNumber.createNormalized(negC, other.exponent);
187
+ const diff = this.exponent - other.exponent;
188
+ if (diff > _ArbitraryNumber.PrecisionCutoff) return this;
189
+ if (diff < -_ArbitraryNumber.PrecisionCutoff) return _ArbitraryNumber.createNormalized(negC, other.exponent);
190
+ let c;
191
+ let e;
192
+ if (diff >= 0) {
193
+ c = this.coefficient + negC / pow10(diff);
194
+ e = this.exponent;
195
+ } else {
196
+ c = negC + this.coefficient / pow10(-diff);
197
+ e = other.exponent;
198
+ }
199
+ return _ArbitraryNumber.normalizeFrom(c, e);
200
+ }
201
+ /**
202
+ * Returns `this * other`.
203
+ *
204
+ * @example
205
+ * new ArbitraryNumber(2, 3).mul(new ArbitraryNumber(3, 4)); // 6*10^7
206
+ */
207
+ mul(other) {
208
+ if (this.coefficient === 0 || other.coefficient === 0) return _ArbitraryNumber.Zero;
209
+ const c = this.coefficient * other.coefficient;
210
+ const e = this.exponent + other.exponent;
211
+ const absC = c < 0 ? -c : c;
212
+ if (absC >= 10) return _ArbitraryNumber.createNormalized(c / 10, e + 1);
213
+ return _ArbitraryNumber.createNormalized(c, e);
214
+ }
215
+ /**
216
+ * Returns `this / other`.
217
+ *
218
+ * @example
219
+ * new ArbitraryNumber(6, 7).div(new ArbitraryNumber(3, 4)); // 2*10^3
220
+ *
221
+ * @throws `"Division by zero"` when `other` is zero.
222
+ */
223
+ div(other) {
224
+ if (other.coefficient === 0) throw new ArbitraryNumberDomainError("Division by zero", { dividend: this.toNumber(), divisor: 0 });
225
+ const c = this.coefficient / other.coefficient;
226
+ const e = this.exponent - other.exponent;
227
+ if (c === 0) return _ArbitraryNumber.Zero;
228
+ const absC = c < 0 ? -c : c;
229
+ if (absC < 1) return _ArbitraryNumber.createNormalized(c * 10, e - 1);
230
+ return _ArbitraryNumber.createNormalized(c, e);
231
+ }
232
+ /**
233
+ * Returns the arithmetic negation of this number (`-this`).
234
+ *
235
+ * @example
236
+ * new ArbitraryNumber(1.5, 3).negate(); // -1.5*10^3
237
+ */
238
+ negate() {
239
+ if (this.coefficient === 0) return _ArbitraryNumber.Zero;
240
+ return _ArbitraryNumber.createNormalized(-this.coefficient, this.exponent);
241
+ }
242
+ /**
243
+ * Returns the absolute value of this number (`|this|`).
244
+ *
245
+ * Returns `this` unchanged when the number is already non-negative.
246
+ *
247
+ * @example
248
+ * new ArbitraryNumber(-1.5, 3).abs(); // 1.5*10^3
249
+ */
250
+ abs() {
251
+ if (this.coefficient >= 0) return this;
252
+ return _ArbitraryNumber.createNormalized(-this.coefficient, this.exponent);
253
+ }
254
+ /**
255
+ * Returns `this^n`.
256
+ *
257
+ * Supports integer, fractional, and negative exponents.
258
+ * `x^0` always returns {@link One}, including `0^0` (by convention).
259
+ *
260
+ * @example
261
+ * new ArbitraryNumber(2, 3).pow(2); // 4*10^6
262
+ * new ArbitraryNumber(2, 0).pow(-1); // 5*10^-1 (= 0.5)
263
+ *
264
+ * @param n - The exponent to raise this number to.
265
+ * @throws `"Zero cannot be raised to a negative power"` when this is zero and `n < 0`.
266
+ */
267
+ pow(n) {
268
+ if (n === 0) {
269
+ return _ArbitraryNumber.One;
270
+ }
271
+ if (this.coefficient === 0) {
272
+ if (n < 0) {
273
+ throw new ArbitraryNumberDomainError("Zero cannot be raised to a negative power", { exponent: n });
274
+ }
275
+ return _ArbitraryNumber.Zero;
276
+ }
277
+ if (this.coefficient < 0 && !Number.isInteger(n)) {
278
+ throw new ArbitraryNumberDomainError("ArbitraryNumber.pow: fractional exponent of a negative base is not supported", { base: this.toNumber(), exponent: n });
279
+ }
280
+ const rawExp = this.exponent * n;
281
+ const intExp = Math.floor(rawExp);
282
+ const fracExp = rawExp - intExp;
283
+ return new _ArbitraryNumber(
284
+ Math.pow(this.coefficient, n) * pow10(fracExp),
285
+ intExp
286
+ );
287
+ }
288
+ /**
289
+ * Fused multiply-add: `(this * multiplier) + addend`.
290
+ *
291
+ * Faster than `.mul(multiplier).add(addend)` because it avoids allocating an
292
+ * intermediate ArbitraryNumber for the product. One normalisation pass total.
293
+ *
294
+ * Common pattern - prestige loop: `value = value.mulAdd(prestigeMultiplier, prestigeBoost)`
295
+ *
296
+ * @example
297
+ * // Equivalent to value.mul(mult).add(boost) but ~35-50% faster
298
+ * const prestiged = currentValue.mulAdd(multiplier, boost);
299
+ */
300
+ mulAdd(multiplier, addend) {
301
+ if (this.coefficient === 0 || multiplier.coefficient === 0) return addend;
302
+ let cp = this.coefficient * multiplier.coefficient;
303
+ let ep = this.exponent + multiplier.exponent;
304
+ const absCp = cp < 0 ? -cp : cp;
305
+ if (absCp >= 10) {
306
+ cp /= 10;
307
+ ep += 1;
308
+ }
309
+ if (addend.coefficient === 0) return _ArbitraryNumber.createNormalized(cp, ep);
310
+ const diff = ep - addend.exponent;
311
+ if (diff > _ArbitraryNumber.PrecisionCutoff) return _ArbitraryNumber.createNormalized(cp, ep);
312
+ if (diff < -_ArbitraryNumber.PrecisionCutoff) return addend;
313
+ let c, e;
314
+ if (diff >= 0) {
315
+ c = cp + addend.coefficient / pow10(diff);
316
+ e = ep;
317
+ } else {
318
+ c = addend.coefficient + cp / pow10(-diff);
319
+ e = addend.exponent;
320
+ }
321
+ return _ArbitraryNumber.normalizeFrom(c, e);
322
+ }
323
+ /**
324
+ * Fused add-multiply: `(this + addend) * multiplier`.
325
+ *
326
+ * Faster than `.add(addend).mul(multiplier)` because it avoids allocating an
327
+ * intermediate ArbitraryNumber for the sum. One normalisation pass total.
328
+ *
329
+ * Common pattern - upgrade calculation: `newValue = baseValue.addMul(bonus, multiplier)`
330
+ *
331
+ * @example
332
+ * // Equivalent to base.add(bonus).mul(multiplier) but ~20-25% faster
333
+ * const upgraded = baseValue.addMul(bonus, multiplier);
334
+ */
335
+ addMul(addend, multiplier) {
336
+ if (multiplier.coefficient === 0) return _ArbitraryNumber.Zero;
337
+ let cs, es;
338
+ if (this.coefficient === 0 && addend.coefficient === 0) return _ArbitraryNumber.Zero;
339
+ if (this.coefficient === 0) {
340
+ cs = addend.coefficient;
341
+ es = addend.exponent;
342
+ } else if (addend.coefficient === 0) {
343
+ cs = this.coefficient;
344
+ es = this.exponent;
345
+ } else {
346
+ const diff = this.exponent - addend.exponent;
347
+ if (diff > _ArbitraryNumber.PrecisionCutoff) {
348
+ cs = this.coefficient;
349
+ es = this.exponent;
350
+ } else if (diff < -_ArbitraryNumber.PrecisionCutoff) {
351
+ cs = addend.coefficient;
352
+ es = addend.exponent;
353
+ } else {
354
+ if (diff >= 0) {
355
+ cs = this.coefficient + addend.coefficient / pow10(diff);
356
+ es = this.exponent;
357
+ } else {
358
+ cs = addend.coefficient + this.coefficient / pow10(-diff);
359
+ es = addend.exponent;
360
+ }
361
+ if (cs === 0) return _ArbitraryNumber.Zero;
362
+ const abs = Math.abs(cs);
363
+ const shift = Math.floor(Math.log10(abs));
364
+ const scale = pow10(shift);
365
+ if (scale === 0) return _ArbitraryNumber.Zero;
366
+ cs /= scale;
367
+ es += shift;
368
+ }
369
+ }
370
+ let cp = cs * multiplier.coefficient;
371
+ const ep = es + multiplier.exponent;
372
+ const absCp = cp < 0 ? -cp : cp;
373
+ if (absCp >= 10) {
374
+ cp /= 10;
375
+ return _ArbitraryNumber.createNormalized(cp, ep + 1);
376
+ }
377
+ return _ArbitraryNumber.createNormalized(cp, ep);
378
+ }
379
+ /**
380
+ * Fused multiply-subtract: `(this * multiplier) - subtrahend`.
381
+ *
382
+ * Avoids one intermediate allocation vs `.mul(multiplier).sub(subtrahend)`.
383
+ *
384
+ * Common pattern - resource drain: `income.mulSub(rate, upkeepCost)`
385
+ */
386
+ mulSub(multiplier, subtrahend) {
387
+ if (this.coefficient === 0 || multiplier.coefficient === 0) {
388
+ if (subtrahend.coefficient === 0) return _ArbitraryNumber.Zero;
389
+ return _ArbitraryNumber.createNormalized(-subtrahend.coefficient, subtrahend.exponent);
390
+ }
391
+ let cp = this.coefficient * multiplier.coefficient;
392
+ let ep = this.exponent + multiplier.exponent;
393
+ const absCp = cp < 0 ? -cp : cp;
394
+ if (absCp >= 10) {
395
+ cp /= 10;
396
+ ep += 1;
397
+ }
398
+ if (subtrahend.coefficient === 0) return _ArbitraryNumber.createNormalized(cp, ep);
399
+ const diff = ep - subtrahend.exponent;
400
+ if (diff > _ArbitraryNumber.PrecisionCutoff) return _ArbitraryNumber.createNormalized(cp, ep);
401
+ if (diff < -_ArbitraryNumber.PrecisionCutoff) {
402
+ return _ArbitraryNumber.createNormalized(-subtrahend.coefficient, subtrahend.exponent);
403
+ }
404
+ let c, e;
405
+ if (diff >= 0) {
406
+ c = cp - subtrahend.coefficient / pow10(diff);
407
+ e = ep;
408
+ } else {
409
+ c = -subtrahend.coefficient + cp / pow10(-diff);
410
+ e = subtrahend.exponent;
411
+ }
412
+ return _ArbitraryNumber.normalizeFrom(c, e);
413
+ }
414
+ /**
415
+ * Fused subtract-multiply: `(this - subtrahend) * multiplier`.
416
+ *
417
+ * Avoids one intermediate allocation vs `.sub(subtrahend).mul(multiplier)`.
418
+ *
419
+ * Common pattern - upgrade after penalty: `health.subMul(damage, multiplier)`
420
+ */
421
+ subMul(subtrahend, multiplier) {
422
+ if (multiplier.coefficient === 0) return _ArbitraryNumber.Zero;
423
+ let cs, es;
424
+ if (this.coefficient === 0 && subtrahend.coefficient === 0) return _ArbitraryNumber.Zero;
425
+ if (this.coefficient === 0) {
426
+ cs = -subtrahend.coefficient;
427
+ es = subtrahend.exponent;
428
+ } else if (subtrahend.coefficient === 0) {
429
+ cs = this.coefficient;
430
+ es = this.exponent;
431
+ } else {
432
+ const diff = this.exponent - subtrahend.exponent;
433
+ if (diff > _ArbitraryNumber.PrecisionCutoff) {
434
+ cs = this.coefficient;
435
+ es = this.exponent;
436
+ } else if (diff < -_ArbitraryNumber.PrecisionCutoff) {
437
+ cs = -subtrahend.coefficient;
438
+ es = subtrahend.exponent;
439
+ } else {
440
+ if (diff >= 0) {
441
+ cs = this.coefficient - subtrahend.coefficient / pow10(diff);
442
+ es = this.exponent;
443
+ } else {
444
+ cs = -subtrahend.coefficient + this.coefficient / pow10(-diff);
445
+ es = subtrahend.exponent;
446
+ }
447
+ if (cs === 0) return _ArbitraryNumber.Zero;
448
+ const abs = Math.abs(cs);
449
+ const shift = Math.floor(Math.log10(abs));
450
+ const scale = pow10(shift);
451
+ if (scale === 0) return _ArbitraryNumber.Zero;
452
+ cs /= scale;
453
+ es += shift;
454
+ }
455
+ }
456
+ let cp = cs * multiplier.coefficient;
457
+ const ep = es + multiplier.exponent;
458
+ const absCp = cp < 0 ? -cp : cp;
459
+ if (absCp >= 10) {
460
+ cp /= 10;
461
+ return _ArbitraryNumber.createNormalized(cp, ep + 1);
462
+ }
463
+ return _ArbitraryNumber.createNormalized(cp, ep);
464
+ }
465
+ /**
466
+ * Fused divide-add: `(this / divisor) + addend`.
467
+ *
468
+ * Avoids one intermediate allocation vs `.div(divisor).add(addend)`.
469
+ *
470
+ * Common pattern - efficiency bonus: `damage.divAdd(armor, flat)`
471
+ *
472
+ * @throws `"Division by zero"` when divisor is zero.
473
+ */
474
+ divAdd(divisor, addend) {
475
+ if (divisor.coefficient === 0) throw new ArbitraryNumberDomainError("Division by zero", { dividend: this.toNumber(), divisor: 0 });
476
+ if (this.coefficient === 0) return addend;
477
+ let cd = this.coefficient / divisor.coefficient;
478
+ let ed = this.exponent - divisor.exponent;
479
+ const absC = cd < 0 ? -cd : cd;
480
+ if (absC < 1) {
481
+ cd *= 10;
482
+ ed -= 1;
483
+ }
484
+ if (addend.coefficient === 0) return _ArbitraryNumber.createNormalized(cd, ed);
485
+ const diff = ed - addend.exponent;
486
+ if (diff > _ArbitraryNumber.PrecisionCutoff) return _ArbitraryNumber.createNormalized(cd, ed);
487
+ if (diff < -_ArbitraryNumber.PrecisionCutoff) return addend;
488
+ let c, e;
489
+ if (diff >= 0) {
490
+ c = cd + addend.coefficient / pow10(diff);
491
+ e = ed;
492
+ } else {
493
+ c = addend.coefficient + cd / pow10(-diff);
494
+ e = addend.exponent;
495
+ }
496
+ return _ArbitraryNumber.normalizeFrom(c, e);
497
+ }
498
+ /**
499
+ * Efficiently sums an array of ArbitraryNumbers in a single normalisation pass.
500
+ *
501
+ * **Why it's fast:** standard chained `.add()` normalises after every element (N log10 calls).
502
+ * `sumArray` aligns all coefficients to the largest exponent (pivot), sums them,
503
+ * then normalises once - regardless of array size.
504
+ *
505
+ * For 50 elements: chained add ~ 50 log10 calls + 50 allocations;
506
+ * sumArray ~ 50 divisions + 1 log10 call + 1 allocation -> ~9* faster.
507
+ *
508
+ * Common pattern - income aggregation: `total = ArbitraryNumber.sumArray(incomeSourcesPerTick)`
509
+ *
510
+ * @example
511
+ * const total = ArbitraryNumber.sumArray(incomeSources); // far faster than .reduce((a, b) => a.add(b))
512
+ *
513
+ * @param numbers - Array to sum. Empty array returns {@link Zero}. Single element returned as-is.
514
+ */
515
+ static sumArray(numbers) {
516
+ const len = numbers.length;
517
+ if (len === 0) return _ArbitraryNumber.Zero;
518
+ if (len === 1) return numbers[0];
519
+ let pivotExp = numbers[0].exponent;
520
+ for (let i = 1; i < len; i++) {
521
+ const n = numbers[i];
522
+ if (n.exponent > pivotExp) pivotExp = n.exponent;
523
+ }
524
+ let total = 0;
525
+ for (let i = 0; i < len; i++) {
526
+ const n = numbers[i];
527
+ if (n.coefficient === 0) continue;
528
+ const diff = pivotExp - n.exponent;
529
+ if (diff > _ArbitraryNumber.PrecisionCutoff) continue;
530
+ total += n.coefficient / pow10(diff);
531
+ }
532
+ if (total === 0) return _ArbitraryNumber.Zero;
533
+ const abs = Math.abs(total);
534
+ const shift = Math.floor(Math.log10(abs));
535
+ const scale = pow10(shift);
536
+ if (scale === 0) return _ArbitraryNumber.Zero;
537
+ return _ArbitraryNumber.createNormalized(total / scale, pivotExp + shift);
538
+ }
539
+ /**
540
+ * Compares this number to `other`.
541
+ *
542
+ * @returns `1` if `this > other`, `-1` if `this < other`, `0` if equal.
543
+ *
544
+ * @example
545
+ * new ArbitraryNumber(1, 4).compareTo(new ArbitraryNumber(9, 3)); // 1 (10000 > 9000)
546
+ * new ArbitraryNumber(-1, 4).compareTo(new ArbitraryNumber(1, 3)); // -1 (-10000 < 1000)
547
+ */
548
+ compareTo(other) {
549
+ const thisNegative = this.coefficient < 0;
550
+ const otherNegative = other.coefficient < 0;
551
+ if (thisNegative !== otherNegative) {
552
+ return thisNegative ? -1 : 1;
553
+ }
554
+ if (this.exponent !== other.exponent) {
555
+ if (this.coefficient === 0) return otherNegative ? 1 : -1;
556
+ if (other.coefficient === 0) return thisNegative ? -1 : 1;
557
+ const thisExponentIsHigher = this.exponent > other.exponent;
558
+ return thisNegative ? thisExponentIsHigher ? -1 : 1 : thisExponentIsHigher ? 1 : -1;
559
+ }
560
+ if (this.coefficient !== other.coefficient) {
561
+ return this.coefficient > other.coefficient ? 1 : -1;
562
+ }
563
+ return 0;
564
+ }
565
+ /** Returns `true` if `this > other`. */
566
+ greaterThan(other) {
567
+ return this.compareTo(other) > 0;
568
+ }
569
+ /** Returns `true` if `this < other`. */
570
+ lessThan(other) {
571
+ return this.compareTo(other) < 0;
572
+ }
573
+ /** Returns `true` if `this >= other`. */
574
+ greaterThanOrEqual(other) {
575
+ return this.compareTo(other) >= 0;
576
+ }
577
+ /** Returns `true` if `this <= other`. */
578
+ lessThanOrEqual(other) {
579
+ return this.compareTo(other) <= 0;
580
+ }
581
+ /** Returns `true` if `this === other` in value. */
582
+ equals(other) {
583
+ return this.compareTo(other) === 0;
584
+ }
585
+ /**
586
+ * Returns the largest integer less than or equal to this number (floor toward -Infinity).
587
+ *
588
+ * Numbers with `exponent >= PrecisionCutoff` are already integers at that scale
589
+ * and are returned unchanged.
590
+ *
591
+ * @example
592
+ * new ArbitraryNumber(1.7, 0).floor(); // 1
593
+ * new ArbitraryNumber(-1.7, 0).floor(); // -2
594
+ */
595
+ floor() {
596
+ if (this.coefficient === 0) {
597
+ return _ArbitraryNumber.Zero;
598
+ }
599
+ if (this.exponent >= _ArbitraryNumber.PrecisionCutoff) {
600
+ return this;
601
+ }
602
+ if (this.exponent < 0) {
603
+ return this.coefficient >= 0 ? _ArbitraryNumber.Zero : new _ArbitraryNumber(-1, 0);
604
+ }
605
+ return new _ArbitraryNumber(Math.floor(this.coefficient * pow10(this.exponent)), 0);
606
+ }
607
+ /**
608
+ * Returns the smallest integer greater than or equal to this number (ceil toward +Infinity).
609
+ *
610
+ * Numbers with `exponent >= PrecisionCutoff` are already integers at that scale
611
+ * and are returned unchanged.
612
+ *
613
+ * @example
614
+ * new ArbitraryNumber(1.2, 0).ceil(); // 2
615
+ * new ArbitraryNumber(-1.7, 0).ceil(); // -1
616
+ */
617
+ ceil() {
618
+ if (this.coefficient === 0) {
619
+ return _ArbitraryNumber.Zero;
620
+ }
621
+ if (this.exponent >= _ArbitraryNumber.PrecisionCutoff) {
622
+ return this;
623
+ }
624
+ if (this.exponent < 0) {
625
+ return this.coefficient > 0 ? _ArbitraryNumber.One : _ArbitraryNumber.Zero;
626
+ }
627
+ return new _ArbitraryNumber(Math.ceil(this.coefficient * pow10(this.exponent)), 0);
628
+ }
629
+ /**
630
+ * Clamps `value` to the inclusive range `[min, max]`.
631
+ *
632
+ * @example
633
+ * ArbitraryNumber.clamp(new ArbitraryNumber(5, 2), new ArbitraryNumber(1, 3), new ArbitraryNumber(2, 3)); // 1*10^3 (500 clamped to [1000, 2000])
634
+ *
635
+ * @param value - The value to clamp.
636
+ * @param min - Lower bound (inclusive).
637
+ * @param max - Upper bound (inclusive).
638
+ */
639
+ static clamp(value, min, max) {
640
+ if (value.lessThan(min)) return min;
641
+ if (value.greaterThan(max)) return max;
642
+ return value;
643
+ }
644
+ /**
645
+ * Returns the smaller of `a` and `b`.
646
+ * @example ArbitraryNumber.min(a, b)
647
+ */
648
+ static min(a, b) {
649
+ return a.lessThan(b) ? a : b;
650
+ }
651
+ /**
652
+ * Returns the larger of `a` and `b`.
653
+ * @example ArbitraryNumber.max(a, b)
654
+ */
655
+ static max(a, b) {
656
+ return a.greaterThan(b) ? a : b;
657
+ }
658
+ /**
659
+ * Linear interpolation: `a + (b - a) * t` where `t in [0, 1]` is a plain number.
660
+ *
661
+ * Used for smooth animations and tweening in game UIs.
662
+ * `t = 0` returns `a`; `t = 1` returns `b`.
663
+ *
664
+ * @param t - Interpolation factor as a plain `number`. Values outside [0, 1] are allowed (extrapolation).
665
+ * @example
666
+ * ArbitraryNumber.lerp(an(100), an(200), 0.5); // 150
667
+ */
668
+ static lerp(a, b, t) {
669
+ if (t === 0) return a;
670
+ if (t === 1) return b;
671
+ return a.add(b.sub(a).mul(_ArbitraryNumber.from(t)));
672
+ }
673
+ /**
674
+ * Runs `fn` with `PrecisionCutoff` temporarily set to `cutoff`, then restores the previous value.
675
+ *
676
+ * Useful when one section of code needs different precision than the rest.
677
+ *
678
+ * @example
679
+ * // Run financial calculation with higher precision
680
+ * const result = ArbitraryNumber.withPrecision(50, () => a.add(b));
681
+ */
682
+ static withPrecision(cutoff, fn) {
683
+ const prev = _ArbitraryNumber.PrecisionCutoff;
684
+ _ArbitraryNumber.PrecisionCutoff = cutoff;
685
+ try {
686
+ return fn();
687
+ } finally {
688
+ _ArbitraryNumber.PrecisionCutoff = prev;
689
+ }
690
+ }
691
+ /**
692
+ * Returns `log10(this)` as a plain JavaScript `number`.
693
+ *
694
+ * Because the number is stored as `c * 10^e`, this is computed exactly as
695
+ * `log10(c) + e` - no precision loss from the exponent.
696
+ *
697
+ * @example
698
+ * new ArbitraryNumber(1, 6).log10(); // 6
699
+ * new ArbitraryNumber(1.5, 3).log10(); // log10(1.5) + 3 ~ 3.176
700
+ *
701
+ * @throws `"Logarithm of zero is undefined"` when this is zero.
702
+ * @throws `"Logarithm of a negative number is undefined"` when this is negative.
703
+ */
704
+ log10() {
705
+ if (this.coefficient === 0) {
706
+ throw new ArbitraryNumberDomainError("Logarithm of zero is undefined", { value: 0 });
707
+ }
708
+ if (this.coefficient < 0) {
709
+ throw new ArbitraryNumberDomainError("Logarithm of a negative number is undefined", { value: this.toNumber() });
710
+ }
711
+ return Math.log10(this.coefficient) + this.exponent;
712
+ }
713
+ /**
714
+ * Returns √this.
715
+ *
716
+ * Computed as pure coefficient math - no `Math.log10` call. Cost: one `Math.sqrt`.
717
+ * For even exponents: `sqrt(c) * 10^(e/2)`.
718
+ * For odd exponents: `sqrt(c * 10) * 10^((e-1)/2)`.
719
+ *
720
+ * @throws `"Square root of negative number"` when this is negative.
721
+ * @example
722
+ * new ArbitraryNumber(4, 0).sqrt(); // 2
723
+ * new ArbitraryNumber(1, 4).sqrt(); // 1*10^2 (= 100)
724
+ */
725
+ sqrt() {
726
+ if (this.coefficient < 0) throw new ArbitraryNumberDomainError("Square root of negative number", { value: this.toNumber() });
727
+ if (this.coefficient === 0) return _ArbitraryNumber.Zero;
728
+ if (this.exponent % 2 !== 0) {
729
+ return _ArbitraryNumber.createNormalized(Math.sqrt(this.coefficient * 10), (this.exponent - 1) / 2);
730
+ }
731
+ return _ArbitraryNumber.createNormalized(Math.sqrt(this.coefficient), this.exponent / 2);
732
+ }
733
+ /**
734
+ * Returns the nearest integer value (rounds half-up).
735
+ *
736
+ * Numbers with `exponent >= PrecisionCutoff` are already integers at that scale
737
+ * and are returned unchanged.
738
+ *
739
+ * @example
740
+ * new ArbitraryNumber(1.5, 0).round(); // 2
741
+ * new ArbitraryNumber(1.4, 0).round(); // 1
742
+ * new ArbitraryNumber(-1.5, 0).round(); // -1 (half-up toward positive infinity)
743
+ */
744
+ round() {
745
+ if (this.coefficient === 0) return _ArbitraryNumber.Zero;
746
+ if (this.exponent >= _ArbitraryNumber.PrecisionCutoff) return this;
747
+ if (this.exponent < 0) {
748
+ if (this.exponent <= -2) return _ArbitraryNumber.Zero;
749
+ const rounded = Math.round(this.coefficient * 0.1);
750
+ return rounded === 0 ? _ArbitraryNumber.Zero : new _ArbitraryNumber(rounded, 0);
751
+ }
752
+ return new _ArbitraryNumber(Math.round(this.coefficient * pow10(this.exponent)), 0);
753
+ }
754
+ /**
755
+ * Returns `1` if positive, `-1` if negative, `0` if zero.
756
+ *
757
+ * @example
758
+ * new ArbitraryNumber(1.5, 3).sign(); // 1
759
+ * new ArbitraryNumber(-1.5, 3).sign(); // -1
760
+ * ArbitraryNumber.Zero.sign(); // 0
761
+ */
762
+ sign() {
763
+ return Math.sign(this.coefficient);
764
+ }
765
+ /**
766
+ * Converts to a plain JavaScript `number`.
767
+ *
768
+ * Precision is limited to float64 (~15 significant digits).
769
+ * Returns `Infinity` for exponents beyond the float64 range (>=308).
770
+ * Returns `0` for exponents below the float64 range (<=-324).
771
+ *
772
+ * @example
773
+ * new ArbitraryNumber(1.5, 3).toNumber(); // 1500
774
+ * new ArbitraryNumber(1, 400).toNumber(); // Infinity
775
+ */
776
+ toNumber() {
777
+ return this.coefficient * pow10(this.exponent);
778
+ }
779
+ /** Returns `true` when this number is zero. */
780
+ isZero() {
781
+ return this.coefficient === 0;
782
+ }
783
+ /** Returns `true` when this number is strictly positive. */
784
+ isPositive() {
785
+ return this.coefficient > 0;
786
+ }
787
+ /** Returns `true` when this number is strictly negative. */
788
+ isNegative() {
789
+ return this.coefficient < 0;
790
+ }
791
+ /**
792
+ * Returns `true` when this number has no fractional part.
793
+ * Numbers with `exponent >= PrecisionCutoff` are always considered integers.
794
+ */
795
+ isInteger() {
796
+ if (this.coefficient === 0) return true;
797
+ if (this.exponent >= _ArbitraryNumber.PrecisionCutoff) return true;
798
+ if (this.exponent < 0) return false;
799
+ return Number.isInteger(this.coefficient * pow10(this.exponent));
800
+ }
801
+ /**
802
+ * Formats this number as a string using the given notation plugin.
803
+ *
804
+ * Defaults to {@link scientificNotation} when no plugin is provided.
805
+ * `decimals` controls the number of decimal places passed to the plugin and defaults to `2`.
806
+ *
807
+ * @example
808
+ * new ArbitraryNumber(1.5, 3).toString(); // "1.50e+3"
809
+ * new ArbitraryNumber(1.5, 3).toString(unitNotation); // "1.50 K"
810
+ * new ArbitraryNumber(1.5, 3).toString(unitNotation, 4); // "1.5000 K"
811
+ *
812
+ * @param notation - The formatting plugin to use.
813
+ * @param decimals - Number of decimal places to render. Defaults to `2`.
814
+ */
815
+ toString(notation = scientificNotation, decimals = 2) {
816
+ return notation.format(this.coefficient, this.exponent, decimals);
817
+ }
818
+ };
819
+ /**
820
+ * Precision cutoff: exponent-difference threshold below which the smaller operand
821
+ * is negligible and silently skipped during addition/subtraction.
822
+ *
823
+ * When |exponent_diff| > PrecisionCutoff, the smaller operand contributes less than
824
+ * 10^-PrecisionCutoff of the result - below float64 coefficient precision for the default of 15.
825
+ *
826
+ * Default: 15 (matches float64 coefficient precision of ~15.95 significant digits).
827
+ * Game patterns: diffs 0-8 (exact), prestige 15-25 (loss <0.0001%), idle 20-50 (~0.1% loss).
828
+ *
829
+ * Override globally via assignment, or use {@link withPrecision} for a scoped block.
830
+ */
831
+ _ArbitraryNumber.PrecisionCutoff = 15;
832
+ /** The additive identity: `0`. */
833
+ _ArbitraryNumber.Zero = new _ArbitraryNumber(0, 0);
834
+ /** The multiplicative identity: `1`. */
835
+ _ArbitraryNumber.One = new _ArbitraryNumber(1, 0);
836
+ /** `10`. */
837
+ _ArbitraryNumber.Ten = new _ArbitraryNumber(1, 1);
838
+ var ArbitraryNumber = _ArbitraryNumber;
839
+
840
+ // src/core/an.ts
841
+ var createAn = ((coefficient, exponent = 0) => new ArbitraryNumber(coefficient, exponent));
842
+ createAn.from = (value) => ArbitraryNumber.from(value);
843
+ var an = createAn;
844
+
845
+ // src/core/AnChain.ts
846
+ var AnChain = class _AnChain {
847
+ constructor(start) {
848
+ this.value = start;
849
+ }
850
+ // ── Construction ────────────────────────────────────────────────────
851
+ /** Creates an `AnChain` from an `ArbitraryNumber` or a plain `number`. */
852
+ static from(value) {
853
+ if (value instanceof ArbitraryNumber) return new _AnChain(value);
854
+ return new _AnChain(ArbitraryNumber.from(value));
855
+ }
856
+ // ── Arithmetic ───────────────────────────────────────────────────────
857
+ /** Adds `other` to the accumulated value. */
858
+ add(other) {
859
+ this.value = this.value.add(other);
860
+ return this;
861
+ }
862
+ /** Subtracts `other` from the accumulated value. */
863
+ sub(other) {
864
+ this.value = this.value.sub(other);
865
+ return this;
866
+ }
867
+ /** Multiplies the accumulated value by `other`. */
868
+ mul(other) {
869
+ this.value = this.value.mul(other);
870
+ return this;
871
+ }
872
+ /** Divides the accumulated value by `other`. */
873
+ div(other) {
874
+ this.value = this.value.div(other);
875
+ return this;
876
+ }
877
+ /** Raises the accumulated value to `exp`. */
878
+ pow(exp) {
879
+ this.value = this.value.pow(exp);
880
+ return this;
881
+ }
882
+ // ── Fused (avoids one intermediate allocation per call) ──────────────
883
+ /** `(this * mult) + add` */
884
+ mulAdd(mult, add) {
885
+ this.value = this.value.mulAdd(mult, add);
886
+ return this;
887
+ }
888
+ /** `(this + add) * mult` */
889
+ addMul(add, mult) {
890
+ this.value = this.value.addMul(add, mult);
891
+ return this;
892
+ }
893
+ /** `(this * mult) - sub` */
894
+ mulSub(mult, sub) {
895
+ this.value = this.value.mulSub(mult, sub);
896
+ return this;
897
+ }
898
+ /** `(this - sub) * mult` */
899
+ subMul(sub, mult) {
900
+ this.value = this.value.subMul(sub, mult);
901
+ return this;
902
+ }
903
+ /** `(this / div) + add` */
904
+ divAdd(div, add) {
905
+ this.value = this.value.divAdd(div, add);
906
+ return this;
907
+ }
908
+ // ── Unary ────────────────────────────────────────────────────────────
909
+ /** Absolute value. */
910
+ abs() {
911
+ this.value = this.value.abs();
912
+ return this;
913
+ }
914
+ /** Negates the accumulated value. */
915
+ neg() {
916
+ this.value = this.value.negate();
917
+ return this;
918
+ }
919
+ /** Square root of the accumulated value. */
920
+ sqrt() {
921
+ this.value = this.value.sqrt();
922
+ return this;
923
+ }
924
+ /** Rounds down to the nearest integer. */
925
+ floor() {
926
+ this.value = this.value.floor();
927
+ return this;
928
+ }
929
+ /** Rounds up to the nearest integer. */
930
+ ceil() {
931
+ this.value = this.value.ceil();
932
+ return this;
933
+ }
934
+ /** Rounds to the nearest integer. */
935
+ round() {
936
+ this.value = this.value.round();
937
+ return this;
938
+ }
939
+ // ── Terminal ─────────────────────────────────────────────────────────
940
+ /** Returns the accumulated `ArbitraryNumber` result. */
941
+ done() {
942
+ return this.value;
943
+ }
944
+ };
945
+ function chain(value) {
946
+ return AnChain.from(value);
947
+ }
948
+
949
+ // src/core/AnFormula.ts
950
+ var AnFormula = class _AnFormula {
951
+ /**
952
+ * Prefer the {@link formula} factory function over calling this directly.
953
+ */
954
+ constructor(name, steps = []) {
955
+ this._name = name;
956
+ this.steps = steps;
957
+ }
958
+ /** The name passed to {@link formula}, if any. */
959
+ get name() {
960
+ return this._name;
961
+ }
962
+ /**
963
+ * Returns a copy of this formula with a new name, leaving the original unchanged.
964
+ *
965
+ * @param name - The new name.
966
+ * @example
967
+ * const base = formula().mul(an(2));
968
+ * const named = base.named("Double");
969
+ * named.name // "Double"
970
+ * base.name // undefined
971
+ */
972
+ named(name) {
973
+ return new _AnFormula(name, this.steps);
974
+ }
975
+ // ── Private helper ────────────────────────────────────────────────────────
976
+ step(fn) {
977
+ return new _AnFormula(this._name, [...this.steps, fn]);
978
+ }
979
+ // ── Arithmetic ────────────────────────────────────────────────────────────
980
+ /** Appends `+ other` to the pipeline. */
981
+ add(other) {
982
+ return this.step((v) => v.add(other));
983
+ }
984
+ /** Appends `- other` to the pipeline. */
985
+ sub(other) {
986
+ return this.step((v) => v.sub(other));
987
+ }
988
+ /** Appends `* other` to the pipeline. */
989
+ mul(other) {
990
+ return this.step((v) => v.mul(other));
991
+ }
992
+ /** Appends `/ other` to the pipeline. */
993
+ div(other) {
994
+ return this.step((v) => v.div(other));
995
+ }
996
+ /** Appends `^ exp` to the pipeline. */
997
+ pow(exp) {
998
+ return this.step((v) => v.pow(exp));
999
+ }
1000
+ // ── Fused (avoids one intermediate allocation per call) ───────────────────
1001
+ /** Appends `(value * mult) + add` to the pipeline. */
1002
+ mulAdd(mult, add) {
1003
+ return this.step((v) => v.mulAdd(mult, add));
1004
+ }
1005
+ /** Appends `(value + add) * mult` to the pipeline. */
1006
+ addMul(add, mult) {
1007
+ return this.step((v) => v.addMul(add, mult));
1008
+ }
1009
+ /** Appends `(value * mult) - sub` to the pipeline. */
1010
+ mulSub(mult, sub) {
1011
+ return this.step((v) => v.mulSub(mult, sub));
1012
+ }
1013
+ /** Appends `(value - sub) * mult` to the pipeline. */
1014
+ subMul(sub, mult) {
1015
+ return this.step((v) => v.subMul(sub, mult));
1016
+ }
1017
+ /** Appends `(value / div) + add` to the pipeline. */
1018
+ divAdd(div, add) {
1019
+ return this.step((v) => v.divAdd(div, add));
1020
+ }
1021
+ // ── Unary ─────────────────────────────────────────────────────────────────
1022
+ /** Appends `abs()` to the pipeline. */
1023
+ abs() {
1024
+ return this.step((v) => v.abs());
1025
+ }
1026
+ /** Appends `neg()` to the pipeline. */
1027
+ neg() {
1028
+ return this.step((v) => v.negate());
1029
+ }
1030
+ /** Appends `sqrt()` to the pipeline. */
1031
+ sqrt() {
1032
+ return this.step((v) => v.sqrt());
1033
+ }
1034
+ /** Appends `floor()` to the pipeline. */
1035
+ floor() {
1036
+ return this.step((v) => v.floor());
1037
+ }
1038
+ /** Appends `ceil()` to the pipeline. */
1039
+ ceil() {
1040
+ return this.step((v) => v.ceil());
1041
+ }
1042
+ /** Appends `round()` to the pipeline. */
1043
+ round() {
1044
+ return this.step((v) => v.round());
1045
+ }
1046
+ // ── Composition ───────────────────────────────────────────────────────────
1047
+ /**
1048
+ * Returns a new formula that first applies `this`, then applies `next`.
1049
+ *
1050
+ * Neither operand is mutated.
1051
+ *
1052
+ * @param next - The formula to apply after `this`.
1053
+ * @example
1054
+ * const full = armorReduction.then(critBonus);
1055
+ * const result = full.apply(baseDamage);
1056
+ */
1057
+ then(next) {
1058
+ return new _AnFormula(this._name, [...this.steps, ...next.steps]);
1059
+ }
1060
+ // ── Terminal ──────────────────────────────────────────────────────────────
1061
+ /**
1062
+ * Runs this formula's pipeline against `value` and returns the result.
1063
+ *
1064
+ * The formula itself is unchanged - call `apply` as many times as needed.
1065
+ *
1066
+ * @param value - The starting value. Plain `number` is coerced via `ArbitraryNumber.from`.
1067
+ * @throws `"ArbitraryNumber.from: value must be finite"` when a plain `number` is non-finite.
1068
+ * @example
1069
+ * const damage = damageFormula.apply(baseDamage);
1070
+ * const scaled = damageFormula.apply(boostedBase);
1071
+ */
1072
+ apply(value) {
1073
+ let result = value instanceof ArbitraryNumber ? value : ArbitraryNumber.from(value);
1074
+ for (const step of this.steps) {
1075
+ result = step(result);
1076
+ }
1077
+ return result;
1078
+ }
1079
+ };
1080
+ function formula(name) {
1081
+ return new AnFormula(name, []);
1082
+ }
1083
+
1084
+ // src/plugin/SuffixNotationBase.ts
1085
+ var SuffixNotationBase = class {
1086
+ /**
1087
+ * @param options - Plugin options. `separator` defaults to `""`.
1088
+ */
1089
+ constructor(options = { separator: "" }) {
1090
+ /** Lookup for remainder values 0, 1, 2 (exponent mod 3). */
1091
+ this.displayScale = [1, 10, 100];
1092
+ this.separator = options.separator ?? "";
1093
+ }
1094
+ /**
1095
+ * Formats the number by combining the scaled coefficient with the suffix returned
1096
+ * by {@link getSuffix}. When `getSuffix` returns an empty string, the separator is
1097
+ * omitted and only the plain value is returned.
1098
+ *
1099
+ * @param coefficient - The significand in `[1, 10)` or `0`.
1100
+ * @param exponent - The power of 10.
1101
+ * @param decimals - Number of decimal places in the output.
1102
+ * @returns The formatted string.
1103
+ */
1104
+ format(coefficient, exponent, decimals) {
1105
+ if (exponent < 0) {
1106
+ return (coefficient * Math.pow(10, exponent)).toFixed(decimals);
1107
+ }
1108
+ const tier = Math.floor(exponent / 3);
1109
+ const remainder = exponent - tier * 3;
1110
+ const displayC = coefficient * this.displayScale[remainder];
1111
+ const suffix = this.getSuffix(tier);
1112
+ if (!suffix) return displayC.toFixed(decimals);
1113
+ return `${displayC.toFixed(decimals)}${this.separator}${suffix}`;
1114
+ }
1115
+ };
1116
+
1117
+ // src/plugin/AlphabetNotation.ts
1118
+ function alphabetSuffix(tier, alphabet = "abcdefghijklmnopqrstuvwxyz") {
1119
+ if (alphabet.length === 0) throw new ArbitraryNumberInputError("alphabet must not be empty", alphabet);
1120
+ if (tier <= 0) return "";
1121
+ const index = tier - 1;
1122
+ let length = 1;
1123
+ let capacity = alphabet.length;
1124
+ let offset = 0;
1125
+ while (index >= offset + capacity) {
1126
+ offset += capacity;
1127
+ length++;
1128
+ capacity = alphabet.length ** length;
1129
+ }
1130
+ let remaining = index - offset;
1131
+ const chars = new Array(length);
1132
+ for (let i = length - 1; i >= 0; i--) {
1133
+ chars[i] = alphabet[remaining % alphabet.length];
1134
+ remaining = Math.floor(remaining / alphabet.length);
1135
+ }
1136
+ return chars.join("");
1137
+ }
1138
+ var AlphabetNotation = class extends SuffixNotationBase {
1139
+ /**
1140
+ * @param options - Plugin options. `alphabet` defaults to `"abcdefghijklmnopqrstuvwxyz"`.
1141
+ * `separator` defaults to `""` (no space between the number and suffix).
1142
+ */
1143
+ constructor(options = {}) {
1144
+ super(options);
1145
+ this.defaultSuffix = "";
1146
+ this.suffixCache = /* @__PURE__ */ new Map();
1147
+ this.alphabet = options.alphabet ?? "abcdefghijklmnopqrstuvwxyz";
1148
+ }
1149
+ /**
1150
+ * Returns the suffix label for the given tier.
1151
+ *
1152
+ * Tier 0 returns `""` - no suffix for values below 10^3.
1153
+ * Tier 1 -> first alphabet symbol, tier N -> last, tier N+1 -> first two-symbol combination.
1154
+ *
1155
+ * Results are cached after the first call per tier.
1156
+ *
1157
+ * @param tier - The exponent tier (`floor(exponent / 3)`), >= 0.
1158
+ * @returns The suffix string, or `""` for tier 0.
1159
+ */
1160
+ getSuffix(tier) {
1161
+ if (tier === 0) {
1162
+ return this.defaultSuffix;
1163
+ }
1164
+ const cached = this.suffixCache.get(tier);
1165
+ if (cached !== void 0) return cached;
1166
+ const result = alphabetSuffix(tier, this.alphabet);
1167
+ this.suffixCache.set(tier, result);
1168
+ return result;
1169
+ }
1170
+ };
1171
+ var letterNotation = new AlphabetNotation();
1172
+
1173
+ // src/constants/units.ts
1174
+ var CLASSIC_UNITS = (() => {
1175
+ const u = [
1176
+ void 0,
1177
+ // tier 0: exponent 0-2 (no unit)
1178
+ { symbol: "K", name: "Thousand" },
1179
+ // tier 1: exponent 3
1180
+ { symbol: "M", name: "Million" },
1181
+ // tier 2: exponent 6
1182
+ { symbol: "B", name: "Billion" },
1183
+ // tier 3: exponent 9
1184
+ { symbol: "T", name: "Trillion" },
1185
+ // tier 4: exponent 12
1186
+ { symbol: "Qa", name: "Quadrillion" },
1187
+ // tier 5: exponent 15
1188
+ { symbol: "Qi", name: "Quintillion" },
1189
+ // tier 6: exponent 18
1190
+ { symbol: "Sx", name: "Sextillion" },
1191
+ // tier 7: exponent 21
1192
+ { symbol: "Sp", name: "Septillion" },
1193
+ // tier 8: exponent 24
1194
+ { symbol: "Oc", name: "Octillion" },
1195
+ // tier 9: exponent 27
1196
+ { symbol: "No", name: "Nonillion" },
1197
+ // tier 10: exponent 30
1198
+ { symbol: "Dc", name: "Decillion" },
1199
+ // tier 11: exponent 33
1200
+ { symbol: "UDc", name: "Undecillion" },
1201
+ // tier 12: exponent 36
1202
+ { symbol: "DDc", name: "Duodecillion" },
1203
+ // tier 13: exponent 39
1204
+ { symbol: "TDc", name: "Tredecillion" },
1205
+ // tier 14: exponent 42
1206
+ { symbol: "QaDc", name: "Quattuordecillion" },
1207
+ // tier 15: exponent 45
1208
+ { symbol: "QiDc", name: "Quindecillion" },
1209
+ // tier 16: exponent 48
1210
+ { symbol: "SxDc", name: "Sexdecillion" },
1211
+ // tier 17: exponent 51
1212
+ { symbol: "SpDc", name: "Septendecillion" },
1213
+ // tier 18: exponent 54
1214
+ { symbol: "OcDc", name: "Octodecillion" },
1215
+ // tier 19: exponent 57
1216
+ { symbol: "NoDc", name: "Novemdecillion" },
1217
+ // tier 20: exponent 60
1218
+ { symbol: "Vg", name: "Vigintillion" },
1219
+ // tier 21: exponent 63
1220
+ { symbol: "UVg", name: "Unvigintillion" },
1221
+ // tier 22: exponent 66
1222
+ { symbol: "DVg", name: "Duovigintillion" },
1223
+ // tier 23: exponent 69
1224
+ { symbol: "TVg", name: "Trevigintillion" },
1225
+ // tier 24: exponent 72
1226
+ { symbol: "QaVg", name: "Quattuorvigintillion" },
1227
+ // tier 25: exponent 75
1228
+ { symbol: "QiVg", name: "Quinvigintillion" },
1229
+ // tier 26: exponent 78
1230
+ { symbol: "SxVg", name: "Sexvigintillion" },
1231
+ // tier 27: exponent 81
1232
+ { symbol: "SpVg", name: "Septenvigintillion" },
1233
+ // tier 28: exponent 84
1234
+ { symbol: "OcVg", name: "Octovigintillion" },
1235
+ // tier 29: exponent 87
1236
+ { symbol: "NoVg", name: "Novemvigintillion" },
1237
+ // tier 30: exponent 90
1238
+ { symbol: "Tg", name: "Trigintillion" },
1239
+ // tier 31: exponent 93
1240
+ { symbol: "UTg", name: "Untrigintillion" },
1241
+ // tier 32: exponent 96
1242
+ { symbol: "DTg", name: "Duotrigintillion" }
1243
+ // tier 33: exponent 99
1244
+ // tiers 34-100: undefined - fallback fires for these exponents
1245
+ ];
1246
+ u[101] = { symbol: "Ct", name: "Centillion" };
1247
+ return Object.freeze(u);
1248
+ })();
1249
+ var COMPACT_UNITS = Object.freeze([
1250
+ void 0,
1251
+ // tier 0: exponent 0-2 (no unit)
1252
+ { symbol: "k" },
1253
+ // tier 1: exponent 3
1254
+ { symbol: "M" },
1255
+ // tier 2: exponent 6
1256
+ { symbol: "B" },
1257
+ // tier 3: exponent 9
1258
+ { symbol: "T" },
1259
+ // tier 4: exponent 12
1260
+ { symbol: "Qa" },
1261
+ // tier 5: exponent 15
1262
+ { symbol: "Qi" },
1263
+ // tier 6: exponent 18
1264
+ { symbol: "Sx" },
1265
+ // tier 7: exponent 21
1266
+ { symbol: "Sp" },
1267
+ // tier 8: exponent 24
1268
+ { symbol: "Oc" },
1269
+ // tier 9: exponent 27
1270
+ { symbol: "No" }
1271
+ // tier 10: exponent 30
1272
+ ]);
1273
+
1274
+ // src/plugin/UnitNotation.ts
1275
+ var UnitNotation = class extends SuffixNotationBase {
1276
+ /**
1277
+ * @param options - Tier-indexed unit array, optional suffix fallback plugin, and separator.
1278
+ * `separator` defaults to `" "` (a space between number and unit symbol).
1279
+ */
1280
+ constructor(options) {
1281
+ super({ separator: " ", ...options });
1282
+ this.units = options.units;
1283
+ this.fallback = options.fallback;
1284
+ }
1285
+ /**
1286
+ * Returns the suffix for the given tier: the own unit symbol if defined,
1287
+ * otherwise the fallback's suffix, otherwise `""`.
1288
+ *
1289
+ * @param tier - The exponent tier (`Math.floor(exponent / 3)`).
1290
+ * @returns The suffix string, or `""` if neither own units nor fallback cover this tier.
1291
+ */
1292
+ getSuffix(tier) {
1293
+ return this.units[tier]?.symbol ?? this.fallback?.getSuffix(tier) ?? "";
1294
+ }
1295
+ };
1296
+ var unitNotation = new UnitNotation({
1297
+ units: CLASSIC_UNITS,
1298
+ fallback: letterNotation
1299
+ });
1300
+
1301
+ // src/utility/ArbitraryNumberGuard.ts
1302
+ var ArbitraryNumberGuard = class _ArbitraryNumberGuard {
1303
+ /**
1304
+ * Returns `true` if `obj` is an instance of {@link ArbitraryNumber}.
1305
+ *
1306
+ * @param obj - The value to test.
1307
+ * @returns `true` when `obj instanceof ArbitraryNumber`.
1308
+ */
1309
+ static isArbitraryNumber(obj) {
1310
+ return obj instanceof ArbitraryNumber;
1311
+ }
1312
+ /**
1313
+ * Returns `true` if `obj` has the shape of a {@link NormalizedNumber}
1314
+ * (i.e. has numeric `coefficient` and `exponent` properties).
1315
+ *
1316
+ * Note: both `ArbitraryNumber` instances and plain objects with the right
1317
+ * shape will pass this check. Use {@link isArbitraryNumber} when you need
1318
+ * to distinguish between the two.
1319
+ *
1320
+ * @param obj - The value to test.
1321
+ * @returns `true` when `obj` has `typeof coefficient === "number"` and
1322
+ * `typeof exponent === "number"`.
1323
+ */
1324
+ static isNormalizedNumber(obj) {
1325
+ return obj != null && typeof obj?.coefficient === "number" && typeof obj?.exponent === "number";
1326
+ }
1327
+ /**
1328
+ * Returns `true` if `obj` is an {@link ArbitraryNumber} with a value of zero.
1329
+ *
1330
+ * @param obj - The value to test.
1331
+ * @returns `true` when `obj` is an `ArbitraryNumber` and its `coefficient` is `0`.
1332
+ */
1333
+ static isZero(obj) {
1334
+ return _ArbitraryNumberGuard.isArbitraryNumber(obj) && obj.coefficient === 0;
1335
+ }
1336
+ };
1337
+
1338
+ // src/utility/ArbitraryNumberOps.ts
1339
+ var ArbitraryNumberOps = class _ArbitraryNumberOps {
1340
+ /**
1341
+ * Converts `value` to an `ArbitraryNumber`, returning it unchanged if it already is one.
1342
+ *
1343
+ * @param value - A plain `number` or an existing `ArbitraryNumber`.
1344
+ * @returns The corresponding `ArbitraryNumber`.
1345
+ */
1346
+ static from(value) {
1347
+ return value instanceof ArbitraryNumber ? value : ArbitraryNumber.from(value);
1348
+ }
1349
+ /**
1350
+ * Returns `left + right`, coercing both operands as needed.
1351
+ *
1352
+ * @param left - The augend.
1353
+ * @param right - The addend.
1354
+ * @example
1355
+ * ops.add(1500, 2500) // ArbitraryNumber (4000)
1356
+ */
1357
+ static add(left, right) {
1358
+ return _ArbitraryNumberOps.from(left).add(_ArbitraryNumberOps.from(right));
1359
+ }
1360
+ /**
1361
+ * Returns `left - right`, coercing both operands as needed.
1362
+ *
1363
+ * @param left - The minuend.
1364
+ * @param right - The subtrahend.
1365
+ * @example
1366
+ * ops.sub(5000, 1500) // ArbitraryNumber (3500)
1367
+ */
1368
+ static sub(left, right) {
1369
+ return _ArbitraryNumberOps.from(left).sub(_ArbitraryNumberOps.from(right));
1370
+ }
1371
+ /**
1372
+ * Returns `left * right`, coercing both operands as needed.
1373
+ *
1374
+ * @param left - The multiplicand.
1375
+ * @param right - The multiplier.
1376
+ * @example
1377
+ * ops.mul(an(1, 3), 5) // ArbitraryNumber (5000)
1378
+ */
1379
+ static mul(left, right) {
1380
+ return _ArbitraryNumberOps.from(left).mul(_ArbitraryNumberOps.from(right));
1381
+ }
1382
+ /**
1383
+ * Returns `left / right`, coercing both operands as needed.
1384
+ *
1385
+ * @param left - The dividend.
1386
+ * @param right - The divisor.
1387
+ * @throws `"Division by zero"` when `right` is zero.
1388
+ * @example
1389
+ * ops.div(an(1, 6), 1000) // ArbitraryNumber (1000)
1390
+ */
1391
+ static div(left, right) {
1392
+ return _ArbitraryNumberOps.from(left).div(_ArbitraryNumberOps.from(right));
1393
+ }
1394
+ /**
1395
+ * Compares `left` to `right`.
1396
+ *
1397
+ * @param left - The left operand.
1398
+ * @param right - The right operand.
1399
+ * @returns `1` if `left > right`, `-1` if `left < right`, `0` if equal.
1400
+ * @example
1401
+ * ops.compare(5000, 1500) // 1
1402
+ */
1403
+ static compare(left, right) {
1404
+ return _ArbitraryNumberOps.from(left).compareTo(_ArbitraryNumberOps.from(right));
1405
+ }
1406
+ /**
1407
+ * Clamps `value` to the inclusive range `[min, max]`, coercing all inputs as needed.
1408
+ *
1409
+ * @param value - The value to clamp.
1410
+ * @param min - The lower bound (inclusive).
1411
+ * @param max - The upper bound (inclusive).
1412
+ * @example
1413
+ * ops.clamp(500, 1000, 2000) // ArbitraryNumber (1000) - below min, returns min
1414
+ */
1415
+ static clamp(value, min, max) {
1416
+ return ArbitraryNumber.clamp(
1417
+ _ArbitraryNumberOps.from(value),
1418
+ _ArbitraryNumberOps.from(min),
1419
+ _ArbitraryNumberOps.from(max)
1420
+ );
1421
+ }
1422
+ };
1423
+
1424
+ // src/utility/ArbitraryNumberHelpers.ts
1425
+ var ArbitraryNumberHelpers = class _ArbitraryNumberHelpers {
1426
+ static coerce(value) {
1427
+ return ArbitraryNumberOps.from(value);
1428
+ }
1429
+ /**
1430
+ * Returns `true` when `value >= threshold`.
1431
+ *
1432
+ * @param value - The value to test.
1433
+ * @param threshold - The minimum required value.
1434
+ * @returns `true` when `value >= threshold`.
1435
+ * @example
1436
+ * ArbitraryNumberHelpers.meetsOrExceeds(gold, upgradeCost)
1437
+ */
1438
+ static meetsOrExceeds(value, threshold) {
1439
+ return _ArbitraryNumberHelpers.coerce(value).greaterThanOrEqual(_ArbitraryNumberHelpers.coerce(threshold));
1440
+ }
1441
+ /**
1442
+ * Returns the largest whole multiple count of `step` that fits into `total`.
1443
+ *
1444
+ * Equivalent to `floor(total / step)`.
1445
+ *
1446
+ * @param total - The total available amount.
1447
+ * @param step - The cost or size of one unit. Must be greater than zero.
1448
+ * @returns The number of whole units that fit, as an `ArbitraryNumber`.
1449
+ * @throws `"step must be greater than zero"` when `step <= 0`.
1450
+ * @example
1451
+ * const canBuy = ArbitraryNumberHelpers.wholeMultipleCount(gold, upgradeCost);
1452
+ */
1453
+ static wholeMultipleCount(total, step) {
1454
+ const numTotal = _ArbitraryNumberHelpers.coerce(total);
1455
+ const numStep = _ArbitraryNumberHelpers.coerce(step);
1456
+ if (numStep.lessThanOrEqual(ArbitraryNumber.Zero)) {
1457
+ throw new ArbitraryNumberInputError("step must be greater than zero", numStep.toNumber());
1458
+ }
1459
+ if (numTotal.lessThanOrEqual(ArbitraryNumber.Zero)) {
1460
+ return ArbitraryNumber.Zero;
1461
+ }
1462
+ return numTotal.div(numStep).floor();
1463
+ }
1464
+ /**
1465
+ * Returns `value - delta`, clamped to a minimum of `floor` (default `0`).
1466
+ *
1467
+ * @param value - The starting value.
1468
+ * @param delta - The amount to subtract.
1469
+ * @param floor - The minimum result. Defaults to `ArbitraryNumber.Zero`.
1470
+ * @returns `max(value - delta, floor)`.
1471
+ * @example
1472
+ * health = ArbitraryNumberHelpers.subtractWithFloor(health, damage);
1473
+ */
1474
+ static subtractWithFloor(value, delta, floor = ArbitraryNumber.Zero) {
1475
+ const numValue = _ArbitraryNumberHelpers.coerce(value);
1476
+ const numDelta = _ArbitraryNumberHelpers.coerce(delta);
1477
+ const numFloor = _ArbitraryNumberHelpers.coerce(floor);
1478
+ return ArbitraryNumber.clamp(numValue.sub(numDelta), numFloor, numValue);
1479
+ }
1480
+ };
1481
+
1482
+ export { AlphabetNotation, AnChain, AnFormula, ArbitraryNumber, ArbitraryNumberDomainError, ArbitraryNumberError, ArbitraryNumberGuard, ArbitraryNumberHelpers, ArbitraryNumberInputError, ArbitraryNumberOps, CLASSIC_UNITS, COMPACT_UNITS, ScientificNotation, SuffixNotationBase, UnitNotation, alphabetSuffix, an, chain, formula, ArbitraryNumberGuard as guard, ArbitraryNumberHelpers as helpers, letterNotation, ArbitraryNumberOps as ops, scientificNotation, unitNotation };
1483
+ //# sourceMappingURL=index.js.map
1484
+ //# sourceMappingURL=index.js.map