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/README.md +179 -16
- 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.js
CHANGED
|
@@ -9,9 +9,17 @@ var ScientificNotation = class {
|
|
|
9
9
|
* @returns The formatted string.
|
|
10
10
|
*/
|
|
11
11
|
format(coefficient, exponent, decimals) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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 >
|
|
165
|
-
if (diff < -
|
|
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 /
|
|
177
|
+
c = this.coefficient + other.coefficient / pow10Int(diff);
|
|
170
178
|
e = this.exponent;
|
|
171
179
|
} else {
|
|
172
|
-
c = other.coefficient + this.coefficient /
|
|
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 >
|
|
189
|
-
if (diff < -
|
|
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 /
|
|
202
|
+
c = this.coefficient + negC / pow10Int(diff);
|
|
194
203
|
e = this.exponent;
|
|
195
204
|
} else {
|
|
196
|
-
c = negC + this.coefficient /
|
|
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 >
|
|
312
|
-
if (diff < -
|
|
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 /
|
|
325
|
+
c = cp + addend.coefficient / pow10Int(diff);
|
|
316
326
|
e = ep;
|
|
317
327
|
} else {
|
|
318
|
-
c = addend.coefficient + cp /
|
|
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 >
|
|
358
|
+
if (diff > cutoff) {
|
|
348
359
|
cs = this.coefficient;
|
|
349
360
|
es = this.exponent;
|
|
350
|
-
} else if (diff < -
|
|
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 /
|
|
366
|
+
cs = this.coefficient + addend.coefficient / pow10Int(diff);
|
|
356
367
|
es = this.exponent;
|
|
357
368
|
} else {
|
|
358
|
-
cs = addend.coefficient + this.coefficient /
|
|
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 >
|
|
401
|
-
if (diff < -
|
|
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 /
|
|
418
|
+
c = cp - subtrahend.coefficient / pow10Int(diff);
|
|
407
419
|
e = ep;
|
|
408
420
|
} else {
|
|
409
|
-
c = -subtrahend.coefficient + cp /
|
|
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 >
|
|
446
|
+
if (diff > cutoff) {
|
|
434
447
|
cs = this.coefficient;
|
|
435
448
|
es = this.exponent;
|
|
436
|
-
} else if (diff < -
|
|
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 /
|
|
454
|
+
cs = this.coefficient - subtrahend.coefficient / pow10Int(diff);
|
|
442
455
|
es = this.exponent;
|
|
443
456
|
} else {
|
|
444
|
-
cs = -subtrahend.coefficient + this.coefficient /
|
|
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 >
|
|
487
|
-
if (diff < -
|
|
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 /
|
|
504
|
+
c = cd + addend.coefficient / pow10Int(diff);
|
|
491
505
|
e = ed;
|
|
492
506
|
} else {
|
|
493
|
-
c = addend.coefficient + cd /
|
|
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 >
|
|
530
|
-
total += n.coefficient /
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1823
|
+
const result = numValue.sub(numDelta);
|
|
1824
|
+
return result.lessThan(numFloor) ? numFloor : result;
|
|
1479
1825
|
}
|
|
1480
1826
|
};
|
|
1481
1827
|
|