arbitrary-numbers 1.0.2 → 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/dist/index.js CHANGED
@@ -9,9 +9,17 @@ var ScientificNotation = class {
9
9
  * @returns The formatted string.
10
10
  */
11
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)}`;
12
+ let c = coefficient;
13
+ let e = exponent;
14
+ let fixed = c.toFixed(decimals);
15
+ if (Math.abs(parseFloat(fixed)) >= 10) {
16
+ c = c / 10;
17
+ e = e + 1;
18
+ fixed = c.toFixed(decimals);
19
+ }
20
+ if (e === 0) return fixed;
21
+ const sign = e < 0 ? "-" : "+";
22
+ return `${fixed}e${sign}${Math.abs(e)}`;
15
23
  }
16
24
  };
17
25
  var scientificNotation = new ScientificNotation();
@@ -38,6 +46,9 @@ var POW10 = [
38
46
  function pow10(n) {
39
47
  return n >= 0 && n < 16 ? POW10[n] : Math.pow(10, n);
40
48
  }
49
+ function pow10Int(n) {
50
+ return n >= 0 && n < 16 ? POW10[n] : Math.pow(10, n);
51
+ }
41
52
 
42
53
  // src/errors.ts
43
54
  var ArbitraryNumberError = class extends Error {
@@ -74,8 +85,9 @@ var _ArbitraryNumber = class _ArbitraryNumber {
74
85
  * ArbitraryNumber.from(1500); // { coefficient: 1.5, exponent: 3 }
75
86
  * ArbitraryNumber.from(0.005); // { coefficient: 5, exponent: -3 }
76
87
  * ArbitraryNumber.from(0); // ArbitraryNumber.Zero
88
+ * ArbitraryNumber.from(-0); // ArbitraryNumber.Zero (signed zero is normalised to +0)
77
89
  *
78
- * @param value - Any finite number.
90
+ * @param value - Any finite number. Signed zero (`-0`) is treated as `0`.
79
91
  * @throws `"ArbitraryNumber.from: value must be finite"` for `NaN`, `Infinity`, or `-Infinity`.
80
92
  */
81
93
  static from(value) {
@@ -127,19 +139,14 @@ var _ArbitraryNumber = class _ArbitraryNumber {
127
139
  * Do NOT use for unnormalised inputs - call new ArbitraryNumber(c, e) instead.
128
140
  */
129
141
  static createNormalized(coefficient, exponent) {
142
+ if (typeof process !== "undefined" && process.env["NODE_ENV"] !== "production" && coefficient === 0 && exponent !== 0) {
143
+ console.warn(`ArbitraryNumber.createNormalized: zero coefficient with non-zero exponent (${exponent}). Use ArbitraryNumber.Zero instead.`);
144
+ }
130
145
  const n = Object.create(_ArbitraryNumber.prototype);
131
146
  n.coefficient = coefficient;
132
147
  n.exponent = exponent;
133
148
  return n;
134
149
  }
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
150
  static normalizeFrom(c, e) {
144
151
  if (c === 0) return _ArbitraryNumber.Zero;
145
152
  const abs = Math.abs(c);
@@ -160,16 +167,17 @@ var _ArbitraryNumber = class _ArbitraryNumber {
160
167
  add(other) {
161
168
  if (this.coefficient === 0) return other;
162
169
  if (other.coefficient === 0) return this;
170
+ const cutoff = _ArbitraryNumber.PrecisionCutoff;
163
171
  const diff = this.exponent - other.exponent;
164
- if (diff > _ArbitraryNumber.PrecisionCutoff) return this;
165
- if (diff < -_ArbitraryNumber.PrecisionCutoff) return other;
172
+ if (diff > cutoff) return this;
173
+ if (diff < -cutoff) return other;
166
174
  let c;
167
175
  let e;
168
176
  if (diff >= 0) {
169
- c = this.coefficient + other.coefficient / pow10(diff);
177
+ c = this.coefficient + other.coefficient / pow10Int(diff);
170
178
  e = this.exponent;
171
179
  } else {
172
- c = other.coefficient + this.coefficient / pow10(-diff);
180
+ c = other.coefficient + this.coefficient / pow10Int(-diff);
173
181
  e = other.exponent;
174
182
  }
175
183
  return _ArbitraryNumber.normalizeFrom(c, e);
@@ -184,16 +192,17 @@ var _ArbitraryNumber = class _ArbitraryNumber {
184
192
  if (other.coefficient === 0) return this;
185
193
  const negC = -other.coefficient;
186
194
  if (this.coefficient === 0) return _ArbitraryNumber.createNormalized(negC, other.exponent);
195
+ const cutoff = _ArbitraryNumber.PrecisionCutoff;
187
196
  const diff = this.exponent - other.exponent;
188
- if (diff > _ArbitraryNumber.PrecisionCutoff) return this;
189
- if (diff < -_ArbitraryNumber.PrecisionCutoff) return _ArbitraryNumber.createNormalized(negC, other.exponent);
197
+ if (diff > cutoff) return this;
198
+ if (diff < -cutoff) return _ArbitraryNumber.createNormalized(negC, other.exponent);
190
199
  let c;
191
200
  let e;
192
201
  if (diff >= 0) {
193
- c = this.coefficient + negC / pow10(diff);
202
+ c = this.coefficient + negC / pow10Int(diff);
194
203
  e = this.exponent;
195
204
  } else {
196
- c = negC + this.coefficient / pow10(-diff);
205
+ c = negC + this.coefficient / pow10Int(-diff);
197
206
  e = other.exponent;
198
207
  }
199
208
  return _ArbitraryNumber.normalizeFrom(c, e);
@@ -307,15 +316,16 @@ var _ArbitraryNumber = class _ArbitraryNumber {
307
316
  ep += 1;
308
317
  }
309
318
  if (addend.coefficient === 0) return _ArbitraryNumber.createNormalized(cp, ep);
319
+ const cutoff = _ArbitraryNumber.PrecisionCutoff;
310
320
  const diff = ep - addend.exponent;
311
- if (diff > _ArbitraryNumber.PrecisionCutoff) return _ArbitraryNumber.createNormalized(cp, ep);
312
- if (diff < -_ArbitraryNumber.PrecisionCutoff) return addend;
321
+ if (diff > cutoff) return _ArbitraryNumber.createNormalized(cp, ep);
322
+ if (diff < -cutoff) return addend;
313
323
  let c, e;
314
324
  if (diff >= 0) {
315
- c = cp + addend.coefficient / pow10(diff);
325
+ c = cp + addend.coefficient / pow10Int(diff);
316
326
  e = ep;
317
327
  } else {
318
- c = addend.coefficient + cp / pow10(-diff);
328
+ c = addend.coefficient + cp / pow10Int(-diff);
319
329
  e = addend.exponent;
320
330
  }
321
331
  return _ArbitraryNumber.normalizeFrom(c, e);
@@ -343,19 +353,20 @@ var _ArbitraryNumber = class _ArbitraryNumber {
343
353
  cs = this.coefficient;
344
354
  es = this.exponent;
345
355
  } else {
356
+ const cutoff = _ArbitraryNumber.PrecisionCutoff;
346
357
  const diff = this.exponent - addend.exponent;
347
- if (diff > _ArbitraryNumber.PrecisionCutoff) {
358
+ if (diff > cutoff) {
348
359
  cs = this.coefficient;
349
360
  es = this.exponent;
350
- } else if (diff < -_ArbitraryNumber.PrecisionCutoff) {
361
+ } else if (diff < -cutoff) {
351
362
  cs = addend.coefficient;
352
363
  es = addend.exponent;
353
364
  } else {
354
365
  if (diff >= 0) {
355
- cs = this.coefficient + addend.coefficient / pow10(diff);
366
+ cs = this.coefficient + addend.coefficient / pow10Int(diff);
356
367
  es = this.exponent;
357
368
  } else {
358
- cs = addend.coefficient + this.coefficient / pow10(-diff);
369
+ cs = addend.coefficient + this.coefficient / pow10Int(-diff);
359
370
  es = addend.exponent;
360
371
  }
361
372
  if (cs === 0) return _ArbitraryNumber.Zero;
@@ -396,17 +407,18 @@ var _ArbitraryNumber = class _ArbitraryNumber {
396
407
  ep += 1;
397
408
  }
398
409
  if (subtrahend.coefficient === 0) return _ArbitraryNumber.createNormalized(cp, ep);
410
+ const cutoff = _ArbitraryNumber.PrecisionCutoff;
399
411
  const diff = ep - subtrahend.exponent;
400
- if (diff > _ArbitraryNumber.PrecisionCutoff) return _ArbitraryNumber.createNormalized(cp, ep);
401
- if (diff < -_ArbitraryNumber.PrecisionCutoff) {
412
+ if (diff > cutoff) return _ArbitraryNumber.createNormalized(cp, ep);
413
+ if (diff < -cutoff) {
402
414
  return _ArbitraryNumber.createNormalized(-subtrahend.coefficient, subtrahend.exponent);
403
415
  }
404
416
  let c, e;
405
417
  if (diff >= 0) {
406
- c = cp - subtrahend.coefficient / pow10(diff);
418
+ c = cp - subtrahend.coefficient / pow10Int(diff);
407
419
  e = ep;
408
420
  } else {
409
- c = -subtrahend.coefficient + cp / pow10(-diff);
421
+ c = -subtrahend.coefficient + cp / pow10Int(-diff);
410
422
  e = subtrahend.exponent;
411
423
  }
412
424
  return _ArbitraryNumber.normalizeFrom(c, e);
@@ -429,19 +441,20 @@ var _ArbitraryNumber = class _ArbitraryNumber {
429
441
  cs = this.coefficient;
430
442
  es = this.exponent;
431
443
  } else {
444
+ const cutoff = _ArbitraryNumber.PrecisionCutoff;
432
445
  const diff = this.exponent - subtrahend.exponent;
433
- if (diff > _ArbitraryNumber.PrecisionCutoff) {
446
+ if (diff > cutoff) {
434
447
  cs = this.coefficient;
435
448
  es = this.exponent;
436
- } else if (diff < -_ArbitraryNumber.PrecisionCutoff) {
449
+ } else if (diff < -cutoff) {
437
450
  cs = -subtrahend.coefficient;
438
451
  es = subtrahend.exponent;
439
452
  } else {
440
453
  if (diff >= 0) {
441
- cs = this.coefficient - subtrahend.coefficient / pow10(diff);
454
+ cs = this.coefficient - subtrahend.coefficient / pow10Int(diff);
442
455
  es = this.exponent;
443
456
  } else {
444
- cs = -subtrahend.coefficient + this.coefficient / pow10(-diff);
457
+ cs = -subtrahend.coefficient + this.coefficient / pow10Int(-diff);
445
458
  es = subtrahend.exponent;
446
459
  }
447
460
  if (cs === 0) return _ArbitraryNumber.Zero;
@@ -482,19 +495,44 @@ var _ArbitraryNumber = class _ArbitraryNumber {
482
495
  ed -= 1;
483
496
  }
484
497
  if (addend.coefficient === 0) return _ArbitraryNumber.createNormalized(cd, ed);
498
+ const cutoff = _ArbitraryNumber.PrecisionCutoff;
485
499
  const diff = ed - addend.exponent;
486
- if (diff > _ArbitraryNumber.PrecisionCutoff) return _ArbitraryNumber.createNormalized(cd, ed);
487
- if (diff < -_ArbitraryNumber.PrecisionCutoff) return addend;
500
+ if (diff > cutoff) return _ArbitraryNumber.createNormalized(cd, ed);
501
+ if (diff < -cutoff) return addend;
488
502
  let c, e;
489
503
  if (diff >= 0) {
490
- c = cd + addend.coefficient / pow10(diff);
504
+ c = cd + addend.coefficient / pow10Int(diff);
491
505
  e = ed;
492
506
  } else {
493
- c = addend.coefficient + cd / pow10(-diff);
507
+ c = addend.coefficient + cd / pow10Int(-diff);
494
508
  e = addend.exponent;
495
509
  }
496
510
  return _ArbitraryNumber.normalizeFrom(c, e);
497
511
  }
512
+ /**
513
+ * Fused multiply-divide: `(this * multiplier) / divisor`.
514
+ *
515
+ * Avoids one intermediate allocation vs `.mul(multiplier).div(divisor)`.
516
+ * The divisor zero-check is performed before the multiply to avoid unnecessary work.
517
+ *
518
+ * Common pattern - idle-tick math: `(production * deltaTime) / cost`
519
+ *
520
+ * @example
521
+ * // Equivalent to production.mul(deltaTime).div(cost) but ~30-40% faster
522
+ * const consumed = production.mulDiv(deltaTime, cost);
523
+ *
524
+ * @throws `"Division by zero"` when divisor is zero.
525
+ */
526
+ mulDiv(multiplier, divisor) {
527
+ if (divisor.coefficient === 0) throw new ArbitraryNumberDomainError("Division by zero", { dividend: this.toNumber(), divisor: 0 });
528
+ if (this.coefficient === 0 || multiplier.coefficient === 0) return _ArbitraryNumber.Zero;
529
+ const c = this.coefficient * multiplier.coefficient / divisor.coefficient;
530
+ const e = this.exponent + multiplier.exponent - divisor.exponent;
531
+ const absC = c < 0 ? -c : c;
532
+ if (absC >= 10) return _ArbitraryNumber.createNormalized(c / 10, e + 1);
533
+ if (absC < 1) return _ArbitraryNumber.createNormalized(c * 10, e - 1);
534
+ return _ArbitraryNumber.createNormalized(c, e);
535
+ }
498
536
  /**
499
537
  * Efficiently sums an array of ArbitraryNumbers in a single normalisation pass.
500
538
  *
@@ -521,13 +559,14 @@ var _ArbitraryNumber = class _ArbitraryNumber {
521
559
  const n = numbers[i];
522
560
  if (n.exponent > pivotExp) pivotExp = n.exponent;
523
561
  }
562
+ const cutoff = _ArbitraryNumber.PrecisionCutoff;
524
563
  let total = 0;
525
564
  for (let i = 0; i < len; i++) {
526
565
  const n = numbers[i];
527
566
  if (n.coefficient === 0) continue;
528
567
  const diff = pivotExp - n.exponent;
529
- if (diff > _ArbitraryNumber.PrecisionCutoff) continue;
530
- total += n.coefficient / pow10(diff);
568
+ if (diff > cutoff) continue;
569
+ total += n.coefficient / pow10Int(diff);
531
570
  }
532
571
  if (total === 0) return _ArbitraryNumber.Zero;
533
572
  const abs = Math.abs(total);
@@ -536,6 +575,69 @@ var _ArbitraryNumber = class _ArbitraryNumber {
536
575
  if (scale === 0) return _ArbitraryNumber.Zero;
537
576
  return _ArbitraryNumber.createNormalized(total / scale, pivotExp + shift);
538
577
  }
578
+ /**
579
+ * Multiplies an array of `ArbitraryNumber`s in a single pass.
580
+ *
581
+ * Coefficients are multiplied together with intermediate normalisation after each step
582
+ * to keep values in [1, 10). Exponents are summed. One total normalisation at the end.
583
+ *
584
+ * Empty array returns {@link One}. Single element returned as-is.
585
+ *
586
+ * @example
587
+ * ArbitraryNumber.productArray([an(2), an(3), an(4)]); // 24
588
+ */
589
+ static productArray(numbers) {
590
+ const len = numbers.length;
591
+ if (len === 0) return _ArbitraryNumber.One;
592
+ if (len === 1) return numbers[0];
593
+ let c = 1;
594
+ let e = 0;
595
+ for (let i = 0; i < len; i++) {
596
+ const n = numbers[i];
597
+ if (n.coefficient === 0) return _ArbitraryNumber.Zero;
598
+ c *= n.coefficient;
599
+ e += n.exponent;
600
+ if (c >= 10 || c <= -10) {
601
+ const absC = c < 0 ? -c : c;
602
+ const shift = Math.floor(Math.log10(absC));
603
+ c /= pow10(shift);
604
+ e += shift;
605
+ }
606
+ }
607
+ return _ArbitraryNumber.normalizeFrom(c, e);
608
+ }
609
+ /**
610
+ * Returns the largest value in an array.
611
+ *
612
+ * Empty array returns {@link Zero}. Single element returned as-is.
613
+ *
614
+ * @example
615
+ * ArbitraryNumber.maxOfArray([an(1), an(3), an(2)]); // an(3)
616
+ */
617
+ static maxOfArray(numbers) {
618
+ if (numbers.length === 0) return _ArbitraryNumber.Zero;
619
+ let max = numbers[0];
620
+ for (let i = 1; i < numbers.length; i++) {
621
+ if (numbers[i].greaterThan(max)) max = numbers[i];
622
+ }
623
+ return max;
624
+ }
625
+ /**
626
+ * Returns the smallest value in an array.
627
+ *
628
+ * Empty array returns {@link Zero}. Single element returned as-is.
629
+ *
630
+ * @example
631
+ * ArbitraryNumber.minOfArray([an(3), an(1), an(2)]); // an(1)
632
+ */
633
+ static minOfArray(numbers) {
634
+ if (numbers.length === 0) return _ArbitraryNumber.Zero;
635
+ let min = numbers[0];
636
+ for (let i = 1; i < numbers.length; i++) {
637
+ if (numbers[i].lessThan(min)) min = numbers[i];
638
+ }
639
+ return min;
640
+ }
539
641
  /**
540
642
  * Compares this number to `other`.
541
643
  *
@@ -730,6 +832,84 @@ var _ArbitraryNumber = class _ArbitraryNumber {
730
832
  }
731
833
  return _ArbitraryNumber.createNormalized(Math.sqrt(this.coefficient), this.exponent / 2);
732
834
  }
835
+ /**
836
+ * Returns the cube root of this number: `∛this`.
837
+ *
838
+ * Computed without `Math.log10`. For exponents divisible by 3: `cbrt(c) * 10^(e/3)`.
839
+ * For remainder 1: `cbrt(c * 10) * 10^((e-1)/3)`.
840
+ * For remainder 2: `cbrt(c * 100) * 10^((e-2)/3)`.
841
+ *
842
+ * Supports negative numbers (cube root of a negative is negative).
843
+ *
844
+ * @example
845
+ * new ArbitraryNumber(8, 0).cbrt(); // 2
846
+ * new ArbitraryNumber(1, 9).cbrt(); // 1*10^3 (= 1,000)
847
+ * new ArbitraryNumber(-8, 0).cbrt(); // -2
848
+ */
849
+ cbrt() {
850
+ if (this.coefficient === 0) return _ArbitraryNumber.Zero;
851
+ const rem = (this.exponent % 3 + 3) % 3;
852
+ const baseExp = (this.exponent - rem) / 3;
853
+ if (rem === 0) return _ArbitraryNumber.createNormalized(Math.cbrt(this.coefficient), baseExp);
854
+ if (rem === 1) return _ArbitraryNumber.createNormalized(Math.cbrt(this.coefficient * 10), baseExp);
855
+ return _ArbitraryNumber.createNormalized(Math.cbrt(this.coefficient * 100), baseExp);
856
+ }
857
+ /**
858
+ * Returns `log_base(this)` as a plain JavaScript `number`.
859
+ *
860
+ * Computed as `log10(this) / log10(base)`. The numerator is exact due to the
861
+ * stored `coefficient × 10^exponent` form.
862
+ *
863
+ * @param base - The logarithm base. Must be positive and not 1.
864
+ * @throws `"Logarithm of zero is undefined"` when this is zero.
865
+ * @throws `"Logarithm of a negative number is undefined"` when this is negative.
866
+ * @throws `"Logarithm base must be positive and not 1"` for invalid base.
867
+ *
868
+ * @example
869
+ * new ArbitraryNumber(8, 0).log(2); // 3
870
+ * new ArbitraryNumber(1, 6).log(10); // 6
871
+ */
872
+ log(base) {
873
+ if (base <= 0 || base === 1 || !isFinite(base)) {
874
+ throw new ArbitraryNumberDomainError("Logarithm base must be positive and not 1", { base });
875
+ }
876
+ return this.log10() / Math.log10(base);
877
+ }
878
+ /**
879
+ * Returns `ln(this)` — the natural logarithm — as a plain JavaScript `number`.
880
+ *
881
+ * Computed as `log10(this) / log10(e)`.
882
+ *
883
+ * @throws `"Logarithm of zero is undefined"` when this is zero.
884
+ * @throws `"Logarithm of a negative number is undefined"` when this is negative.
885
+ *
886
+ * @example
887
+ * new ArbitraryNumber(1, 0).ln(); // 0
888
+ */
889
+ ln() {
890
+ return this.log10() / Math.LOG10E;
891
+ }
892
+ /**
893
+ * Returns `10^n` as an `ArbitraryNumber`, where `n` is a plain JS `number`.
894
+ *
895
+ * This is the inverse of {@link log10}. Useful for converting a log10 result
896
+ * back into an `ArbitraryNumber`.
897
+ *
898
+ * @example
899
+ * ArbitraryNumber.exp10(6); // 1*10^6 (= 1,000,000)
900
+ * ArbitraryNumber.exp10(3.5); // 10^3.5 ≈ 3162.3
901
+ *
902
+ * @throws `"ArbitraryNumber.exp10: n must be finite"` for non-finite `n`.
903
+ */
904
+ static exp10(n) {
905
+ if (!isFinite(n)) {
906
+ throw new ArbitraryNumberInputError("ArbitraryNumber.exp10: n must be finite", n);
907
+ }
908
+ const intPart = Math.floor(n);
909
+ const fracPart = n - intPart;
910
+ const c = Math.pow(10, fracPart);
911
+ return _ArbitraryNumber.createNormalized(c, intPart);
912
+ }
733
913
  /**
734
914
  * Returns the nearest integer value (rounds half-up).
735
915
  *
@@ -751,6 +931,25 @@ var _ArbitraryNumber = class _ArbitraryNumber {
751
931
  }
752
932
  return new _ArbitraryNumber(Math.round(this.coefficient * pow10(this.exponent)), 0);
753
933
  }
934
+ /**
935
+ * Returns the integer part of this number, truncating toward zero.
936
+ *
937
+ * Unlike `floor()`, which rounds toward −∞, `trunc()` always rounds toward 0:
938
+ * - `trunc(1.7) = 1` (same as floor)
939
+ * - `trunc(-1.7) = -1` (different from floor, which gives -2)
940
+ *
941
+ * Numbers with `exponent >= PrecisionCutoff` are already integers and returned unchanged.
942
+ *
943
+ * @example
944
+ * new ArbitraryNumber(1.7, 0).trunc(); // 1
945
+ * new ArbitraryNumber(-1.7, 0).trunc(); // -1
946
+ */
947
+ trunc() {
948
+ if (this.coefficient === 0) return _ArbitraryNumber.Zero;
949
+ if (this.exponent >= _ArbitraryNumber.PrecisionCutoff) return this;
950
+ if (this.exponent < 0) return _ArbitraryNumber.Zero;
951
+ return new _ArbitraryNumber(Math.trunc(this.coefficient * pow10(this.exponent)), 0);
952
+ }
754
953
  /**
755
954
  * Returns `1` if positive, `-1` if negative, `0` if zero.
756
955
  *
@@ -776,6 +975,95 @@ var _ArbitraryNumber = class _ArbitraryNumber {
776
975
  toNumber() {
777
976
  return this.coefficient * pow10(this.exponent);
778
977
  }
978
+ /**
979
+ * Returns a stable, minimal JSON representation: `{ c: number, e: number }`.
980
+ *
981
+ * The keys `c` and `e` are intentionally short to keep save-game blobs small.
982
+ * Reconstruct via {@link ArbitraryNumber.fromJSON}.
983
+ *
984
+ * Round-trip guarantee: `ArbitraryNumber.fromJSON(x.toJSON()).equals(x)` is always `true`.
985
+ *
986
+ * @example
987
+ * JSON.stringify(an(1.5, 6)); // '{"c":1.5,"e":6}'
988
+ */
989
+ toJSON() {
990
+ return { c: this.coefficient, e: this.exponent };
991
+ }
992
+ /**
993
+ * Returns a raw string representation: `"<coefficient>|<exponent>"`.
994
+ *
995
+ * Useful for compact textual serialization. Reconstruct via {@link ArbitraryNumber.parse}.
996
+ *
997
+ * @example
998
+ * an(1.5, 3).toRaw(); // "1.5|3"
999
+ * an(-2, 6).toRaw(); // "-2|6"
1000
+ */
1001
+ toRaw() {
1002
+ return `${this.coefficient}|${this.exponent}`;
1003
+ }
1004
+ /**
1005
+ * Reconstructs an `ArbitraryNumber` from a `toJSON()` blob.
1006
+ *
1007
+ * @example
1008
+ * const n = an(1.5, 6);
1009
+ * ArbitraryNumber.fromJSON(n.toJSON()).equals(n); // true
1010
+ *
1011
+ * @param obj - Object with numeric `c` (coefficient) and `e` (exponent) fields.
1012
+ * @throws `ArbitraryNumberInputError` when the object shape is invalid or values are non-finite.
1013
+ */
1014
+ static fromJSON(obj) {
1015
+ if (obj === null || typeof obj !== "object" || !("c" in obj) || !("e" in obj) || typeof obj.c !== "number" || typeof obj.e !== "number") {
1016
+ throw new ArbitraryNumberInputError(
1017
+ "ArbitraryNumber.fromJSON: expected { c: number, e: number }",
1018
+ String(obj)
1019
+ );
1020
+ }
1021
+ const { c, e } = obj;
1022
+ if (!isFinite(c)) {
1023
+ throw new ArbitraryNumberInputError("ArbitraryNumber.fromJSON: c must be finite", c);
1024
+ }
1025
+ if (!isFinite(e)) {
1026
+ throw new ArbitraryNumberInputError("ArbitraryNumber.fromJSON: e must be finite", e);
1027
+ }
1028
+ return _ArbitraryNumber.normalizeFrom(c, e);
1029
+ }
1030
+ /**
1031
+ * Parses a string into an `ArbitraryNumber`.
1032
+ *
1033
+ * Accepted formats:
1034
+ * - Raw pipe format (exact round-trip): `"1.5|3"`, `"-2.5|-6"`
1035
+ * - Standard scientific notation: `"1.5e+3"`, `"1.5e-3"`, `"1.5E3"`, `"1.5e3"`
1036
+ * - Plain decimal: `"1500"`, `"-0.003"`, `"0"`
1037
+ *
1038
+ * @example
1039
+ * ArbitraryNumber.parse("1.5|3"); // same as an(1.5, 3)
1040
+ * ArbitraryNumber.parse("1.5e+3"); // same as ArbitraryNumber.from(1500)
1041
+ * ArbitraryNumber.parse("1500"); // same as ArbitraryNumber.from(1500)
1042
+ *
1043
+ * @throws `ArbitraryNumberInputError` when the string cannot be parsed or produces a non-finite value.
1044
+ */
1045
+ static parse(s) {
1046
+ if (typeof s !== "string" || s.trim() === "") {
1047
+ throw new ArbitraryNumberInputError("ArbitraryNumber.parse: input must be a non-empty string", s);
1048
+ }
1049
+ const trimmed = s.trim();
1050
+ const pipeIdx = trimmed.indexOf("|");
1051
+ if (pipeIdx !== -1) {
1052
+ const cStr = trimmed.slice(0, pipeIdx);
1053
+ const eStr = trimmed.slice(pipeIdx + 1);
1054
+ const c = Number(cStr);
1055
+ const e = Number(eStr);
1056
+ if (!isFinite(c) || !isFinite(e) || cStr === "" || eStr === "") {
1057
+ throw new ArbitraryNumberInputError("ArbitraryNumber.parse: invalid pipe format", s);
1058
+ }
1059
+ return _ArbitraryNumber.normalizeFrom(c, e);
1060
+ }
1061
+ const n = Number(trimmed);
1062
+ if (!isFinite(n)) {
1063
+ throw new ArbitraryNumberInputError("ArbitraryNumber.parse: value is not finite", s);
1064
+ }
1065
+ return new _ArbitraryNumber(n, 0);
1066
+ }
779
1067
  /** Returns `true` when this number is zero. */
780
1068
  isZero() {
781
1069
  return this.coefficient === 0;
@@ -798,6 +1086,22 @@ var _ArbitraryNumber = class _ArbitraryNumber {
798
1086
  if (this.exponent < 0) return false;
799
1087
  return Number.isInteger(this.coefficient * pow10(this.exponent));
800
1088
  }
1089
+ /**
1090
+ * Allows implicit coercion via `+an(1500)` (returns `toNumber()`) and
1091
+ * template literals / string concatenation (returns `toString()`).
1092
+ *
1093
+ * `hint === "number"` → `toNumber()`; all other hints → `toString()`.
1094
+ */
1095
+ [Symbol.toPrimitive](hint) {
1096
+ return hint === "number" ? this.toNumber() : this.toString();
1097
+ }
1098
+ /**
1099
+ * Custom Node.js inspect output so `console.log(an(1500))` renders `"1.50e+3"`
1100
+ * instead of the raw object representation.
1101
+ */
1102
+ [/* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom")]() {
1103
+ return this.toString();
1104
+ }
801
1105
  /**
802
1106
  * Formats this number as a string using the given notation plugin.
803
1107
  *
@@ -1103,7 +1407,7 @@ var SuffixNotationBase = class {
1103
1407
  */
1104
1408
  format(coefficient, exponent, decimals) {
1105
1409
  if (exponent < 0) {
1106
- return (coefficient * Math.pow(10, exponent)).toFixed(decimals);
1410
+ return (coefficient / pow10(-exponent)).toFixed(decimals);
1107
1411
  }
1108
1412
  const tier = Math.floor(exponent / 3);
1109
1413
  const remainder = exponent - tier * 3;
@@ -1118,6 +1422,7 @@ var SuffixNotationBase = class {
1118
1422
  function alphabetSuffix(tier, alphabet = "abcdefghijklmnopqrstuvwxyz") {
1119
1423
  if (alphabet.length === 0) throw new ArbitraryNumberInputError("alphabet must not be empty", alphabet);
1120
1424
  if (tier <= 0) return "";
1425
+ if (alphabet.length === 1) return alphabet.repeat(tier);
1121
1426
  const index = tier - 1;
1122
1427
  let length = 1;
1123
1428
  let capacity = alphabet.length;
@@ -1276,21 +1581,40 @@ var UnitNotation = class extends SuffixNotationBase {
1276
1581
  /**
1277
1582
  * @param options - Tier-indexed unit array, optional suffix fallback plugin, and separator.
1278
1583
  * `separator` defaults to `" "` (a space between number and unit symbol).
1584
+ * `offsetFallback` defaults to `true` — the fallback tier is offset so its suffixes
1585
+ * are visually distinct from any low-tier suffixes the same fallback would produce.
1279
1586
  */
1280
1587
  constructor(options) {
1281
1588
  super({ separator: " ", ...options });
1282
1589
  this.units = options.units;
1283
1590
  this.fallback = options.fallback;
1591
+ this.offsetFallback = options.offsetFallback !== false;
1592
+ let last = 0;
1593
+ for (let i = options.units.length - 1; i >= 0; i--) {
1594
+ if (options.units[i] !== void 0) {
1595
+ last = i;
1596
+ break;
1597
+ }
1598
+ }
1599
+ this.lastDefinedTier = last;
1284
1600
  }
1285
1601
  /**
1286
1602
  * Returns the suffix for the given tier: the own unit symbol if defined,
1287
- * otherwise the fallback's suffix, otherwise `""`.
1603
+ * otherwise the fallback's suffix (offset by the last defined tier when
1604
+ * `offsetFallback` is `true`), otherwise `""`.
1605
+ *
1606
+ * The offset ensures fallback suffixes start at tier 1 of the fallback's sequence,
1607
+ * avoiding visual ambiguity with low-tier suffixes from the same fallback plugin.
1288
1608
  *
1289
1609
  * @param tier - The exponent tier (`Math.floor(exponent / 3)`).
1290
1610
  * @returns The suffix string, or `""` if neither own units nor fallback cover this tier.
1291
1611
  */
1292
1612
  getSuffix(tier) {
1293
- return this.units[tier]?.symbol ?? this.fallback?.getSuffix(tier) ?? "";
1613
+ const ownSymbol = this.units[tier]?.symbol;
1614
+ if (ownSymbol !== void 0) return ownSymbol;
1615
+ if (this.fallback === void 0) return "";
1616
+ const fallbackTier = this.offsetFallback ? tier - this.lastDefinedTier : tier;
1617
+ return this.fallback.getSuffix(fallbackTier) ?? "";
1294
1618
  }
1295
1619
  };
1296
1620
  var unitNotation = new UnitNotation({
@@ -1342,10 +1666,31 @@ var ArbitraryNumberOps = class _ArbitraryNumberOps {
1342
1666
  *
1343
1667
  * @param value - A plain `number` or an existing `ArbitraryNumber`.
1344
1668
  * @returns The corresponding `ArbitraryNumber`.
1669
+ * @throws `ArbitraryNumberInputError` when `value` is `NaN`, `Infinity`, or `-Infinity`.
1345
1670
  */
1346
1671
  static from(value) {
1347
1672
  return value instanceof ArbitraryNumber ? value : ArbitraryNumber.from(value);
1348
1673
  }
1674
+ /**
1675
+ * Converts `value` to an `ArbitraryNumber`, returning `null` for non-finite inputs
1676
+ * instead of throwing.
1677
+ *
1678
+ * Use this at system boundaries (form inputs, external APIs) where you want to handle
1679
+ * bad input gracefully rather than catching an exception.
1680
+ *
1681
+ * @param value - A plain `number` or an existing `ArbitraryNumber`.
1682
+ * @returns The `ArbitraryNumber`, or `null` if the input is `NaN`, `Infinity`, or `-Infinity`.
1683
+ *
1684
+ * @example
1685
+ * ops.tryFrom(1500) // ArbitraryNumber { coefficient: 1.5, exponent: 3 }
1686
+ * ops.tryFrom(Infinity) // null
1687
+ * ops.tryFrom(NaN) // null
1688
+ */
1689
+ static tryFrom(value) {
1690
+ if (value instanceof ArbitraryNumber) return value;
1691
+ if (!isFinite(value)) return null;
1692
+ return ArbitraryNumber.from(value);
1693
+ }
1349
1694
  /**
1350
1695
  * Returns `left + right`, coercing both operands as needed.
1351
1696
  *
@@ -1475,7 +1820,8 @@ var ArbitraryNumberHelpers = class _ArbitraryNumberHelpers {
1475
1820
  const numValue = _ArbitraryNumberHelpers.coerce(value);
1476
1821
  const numDelta = _ArbitraryNumberHelpers.coerce(delta);
1477
1822
  const numFloor = _ArbitraryNumberHelpers.coerce(floor);
1478
- return ArbitraryNumber.clamp(numValue.sub(numDelta), numFloor, numValue);
1823
+ const result = numValue.sub(numDelta);
1824
+ return result.lessThan(numFloor) ? numFloor : result;
1479
1825
  }
1480
1826
  };
1481
1827