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