arbitrary-numbers 1.0.2 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +480 -375
- package/dist/index.cjs +970 -696
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +441 -505
- package/dist/index.d.ts +441 -505
- package/dist/index.js +969 -693
- 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();
|
|
@@ -35,10 +43,17 @@ var POW10 = [
|
|
|
35
43
|
1e12,
|
|
36
44
|
1e13,
|
|
37
45
|
1e14,
|
|
38
|
-
1e15
|
|
46
|
+
1e15,
|
|
47
|
+
1e16,
|
|
48
|
+
1e17,
|
|
49
|
+
1e18,
|
|
50
|
+
1e19,
|
|
51
|
+
1e20,
|
|
52
|
+
1e21,
|
|
53
|
+
1e22
|
|
39
54
|
];
|
|
40
55
|
function pow10(n) {
|
|
41
|
-
return n >= 0 && n <
|
|
56
|
+
return n >= 0 && n < 23 ? POW10[n] : Math.pow(10, n);
|
|
42
57
|
}
|
|
43
58
|
|
|
44
59
|
// src/errors.ts
|
|
@@ -62,41 +77,27 @@ var ArbitraryNumberDomainError = class extends ArbitraryNumberError {
|
|
|
62
77
|
this.context = context;
|
|
63
78
|
}
|
|
64
79
|
};
|
|
80
|
+
var ArbitraryNumberMutationError = class extends ArbitraryNumberDomainError {
|
|
81
|
+
constructor(message) {
|
|
82
|
+
super(message, {});
|
|
83
|
+
this.name = "ArbitraryNumberMutationError";
|
|
84
|
+
}
|
|
85
|
+
};
|
|
65
86
|
|
|
66
87
|
// src/core/ArbitraryNumber.ts
|
|
88
|
+
var _scaleCutoff = 15;
|
|
67
89
|
var _ArbitraryNumber = class _ArbitraryNumber {
|
|
68
|
-
/**
|
|
69
|
-
* Creates an `ArbitraryNumber` from a plain JavaScript `number`.
|
|
70
|
-
*
|
|
71
|
-
* Prefer this over `new ArbitraryNumber(value, 0)` when working with
|
|
72
|
-
* ordinary numeric literals - it reads clearly at the call site and
|
|
73
|
-
* validates the input.
|
|
74
|
-
*
|
|
75
|
-
* @example
|
|
76
|
-
* ArbitraryNumber.from(1500); // { coefficient: 1.5, exponent: 3 }
|
|
77
|
-
* ArbitraryNumber.from(0.005); // { coefficient: 5, exponent: -3 }
|
|
78
|
-
* ArbitraryNumber.from(0); // ArbitraryNumber.Zero
|
|
79
|
-
*
|
|
80
|
-
* @param value - Any finite number.
|
|
81
|
-
* @throws `"ArbitraryNumber.from: value must be finite"` for `NaN`, `Infinity`, or `-Infinity`.
|
|
82
|
-
*/
|
|
83
|
-
static from(value) {
|
|
84
|
-
if (!isFinite(value)) {
|
|
85
|
-
throw new ArbitraryNumberInputError("ArbitraryNumber.from: value must be finite", value);
|
|
86
|
-
}
|
|
87
|
-
return new _ArbitraryNumber(value, 0);
|
|
88
|
-
}
|
|
89
90
|
/**
|
|
90
91
|
* Constructs a new `ArbitraryNumber` and immediately normalises it so that
|
|
91
92
|
* `1 <= |coefficient| < 10` (or `coefficient === 0`).
|
|
92
93
|
*
|
|
94
|
+
* Always two numeric arguments — this keeps the constructor monomorphic so V8
|
|
95
|
+
* locks in a hidden class on first use (~5 ns allocation).
|
|
96
|
+
*
|
|
93
97
|
* @example
|
|
94
98
|
* new ArbitraryNumber(15, 3); // stored as { coefficient: 1.5, exponent: 4 }
|
|
95
99
|
*
|
|
96
|
-
* @
|
|
97
|
-
* @param exponent - The power of 10. Must be a finite number.
|
|
98
|
-
* @throws `"ArbitraryNumber: coefficient must be finite"` for `NaN`, `Infinity`, or `-Infinity`.
|
|
99
|
-
* @throws `"ArbitraryNumber: exponent must be finite"` for non-finite exponents.
|
|
100
|
+
* @throws `ArbitraryNumberInputError` for `NaN`/`Infinity` coefficient or exponent.
|
|
100
101
|
*/
|
|
101
102
|
constructor(coefficient, exponent) {
|
|
102
103
|
if (coefficient === 0) {
|
|
@@ -122,185 +123,329 @@ var _ArbitraryNumber = class _ArbitraryNumber {
|
|
|
122
123
|
this.exponent = exponent + shift;
|
|
123
124
|
}
|
|
124
125
|
/**
|
|
125
|
-
* @internal Fast-path factory for already-normalised values.
|
|
126
|
+
* @internal Fast-path factory for already-normalised values. Not exported from `index.ts`.
|
|
126
127
|
*
|
|
127
|
-
*
|
|
128
|
-
* Only valid when
|
|
129
|
-
*
|
|
128
|
+
* Allocates a new instance and writes the two fields directly — bypasses validation
|
|
129
|
+
* and normalisation. Only valid when `|coefficient|` is already in `[1, 10)` (or 0)
|
|
130
|
+
* and `exponent` is correct.
|
|
130
131
|
*/
|
|
131
|
-
static
|
|
132
|
-
const n =
|
|
132
|
+
static unsafe(coefficient, exponent) {
|
|
133
|
+
const n = new _ArbitraryNumber(0, 0);
|
|
133
134
|
n.coefficient = coefficient;
|
|
134
135
|
n.exponent = exponent;
|
|
135
136
|
return n;
|
|
136
137
|
}
|
|
137
|
-
/**
|
|
138
|
-
|
|
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
|
-
static normalizeFrom(c, e) {
|
|
138
|
+
/** @internal Normalise an arbitrary `(c, e)` pair into a new instance. */
|
|
139
|
+
static _normalizeNew(c, e) {
|
|
146
140
|
if (c === 0) return _ArbitraryNumber.Zero;
|
|
147
141
|
const abs = Math.abs(c);
|
|
148
142
|
const shift = Math.floor(Math.log10(abs));
|
|
149
143
|
const scale = pow10(shift);
|
|
150
144
|
if (scale === 0) return _ArbitraryNumber.Zero;
|
|
151
|
-
return _ArbitraryNumber.
|
|
145
|
+
return _ArbitraryNumber.unsafe(c / scale, e + shift);
|
|
146
|
+
}
|
|
147
|
+
/** @internal Normalise `(c, e)` into `this` (mutates). Returns `this`. */
|
|
148
|
+
_normalizeInto(c, e) {
|
|
149
|
+
if (c === 0) {
|
|
150
|
+
this.coefficient = 0;
|
|
151
|
+
this.exponent = 0;
|
|
152
|
+
return this;
|
|
153
|
+
}
|
|
154
|
+
const abs = c < 0 ? -c : c;
|
|
155
|
+
if (abs < 10) {
|
|
156
|
+
if (abs >= 1) {
|
|
157
|
+
this.coefficient = c;
|
|
158
|
+
this.exponent = e;
|
|
159
|
+
return this;
|
|
160
|
+
}
|
|
161
|
+
this.coefficient = c * 10;
|
|
162
|
+
this.exponent = e - 1;
|
|
163
|
+
return this;
|
|
164
|
+
}
|
|
165
|
+
if (abs < 100) {
|
|
166
|
+
this.coefficient = c / 10;
|
|
167
|
+
this.exponent = e + 1;
|
|
168
|
+
return this;
|
|
169
|
+
}
|
|
170
|
+
if (!isFinite(c)) {
|
|
171
|
+
this.coefficient = 0;
|
|
172
|
+
this.exponent = 0;
|
|
173
|
+
return this;
|
|
174
|
+
}
|
|
175
|
+
const shift = Math.floor(Math.log10(abs));
|
|
176
|
+
const scale = pow10(shift);
|
|
177
|
+
if (scale === 0) {
|
|
178
|
+
this.coefficient = 0;
|
|
179
|
+
this.exponent = 0;
|
|
180
|
+
return this;
|
|
181
|
+
}
|
|
182
|
+
this.coefficient = c / scale;
|
|
183
|
+
this.exponent = e + shift;
|
|
184
|
+
return this;
|
|
152
185
|
}
|
|
186
|
+
// ── Factories ──────────────────────────────────────────────────────────────
|
|
153
187
|
/**
|
|
154
|
-
* Returns
|
|
188
|
+
* Returns a fresh, unfrozen copy of this number. The canonical way to preserve
|
|
189
|
+
* a value before mutating it:
|
|
155
190
|
*
|
|
156
|
-
*
|
|
157
|
-
*
|
|
191
|
+
* ```ts
|
|
192
|
+
* const before = gold.clone();
|
|
193
|
+
* gold.add(drop);
|
|
194
|
+
* ```
|
|
195
|
+
*/
|
|
196
|
+
clone() {
|
|
197
|
+
return _ArbitraryNumber.unsafe(this.coefficient, this.exponent);
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Creates an `ArbitraryNumber` from a plain JavaScript `number`.
|
|
201
|
+
*
|
|
202
|
+
* @throws `ArbitraryNumberInputError` for `NaN`, `Infinity`, or `-Infinity`.
|
|
203
|
+
*
|
|
204
|
+
* @example
|
|
205
|
+
* ArbitraryNumber.from(1500); // { coefficient: 1.5, exponent: 3 }
|
|
206
|
+
*/
|
|
207
|
+
static from(value) {
|
|
208
|
+
if (!isFinite(value)) {
|
|
209
|
+
throw new ArbitraryNumberInputError("ArbitraryNumber.from: value must be finite", value);
|
|
210
|
+
}
|
|
211
|
+
return new _ArbitraryNumber(value, 0);
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Like `from`, but returns `null` instead of throwing for non-finite inputs.
|
|
215
|
+
*
|
|
216
|
+
* Use at system boundaries (form inputs, external APIs) where bad input should
|
|
217
|
+
* be handled gracefully.
|
|
218
|
+
*
|
|
219
|
+
* @example
|
|
220
|
+
* ArbitraryNumber.tryFrom(Infinity) // null
|
|
221
|
+
* ArbitraryNumber.tryFrom(1500) // ArbitraryNumber { coefficient: 1.5, exponent: 3 }
|
|
222
|
+
*/
|
|
223
|
+
static tryFrom(value) {
|
|
224
|
+
if (!isFinite(value)) return null;
|
|
225
|
+
return new _ArbitraryNumber(value, 0);
|
|
226
|
+
}
|
|
227
|
+
// ── Static arithmetic (allocate) ───────────────────────────────────────────
|
|
228
|
+
/**
|
|
229
|
+
* Returns a **new** instance equal to `a + b`.
|
|
230
|
+
*
|
|
231
|
+
* Static methods always allocate — use instance `.add()` on hot paths.
|
|
232
|
+
*/
|
|
233
|
+
static add(a, b) {
|
|
234
|
+
return a.clone().add(b);
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Returns a **new** instance equal to `a - b`.
|
|
238
|
+
*/
|
|
239
|
+
static sub(a, b) {
|
|
240
|
+
return a.clone().sub(b);
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Returns a **new** instance equal to `a * b`.
|
|
244
|
+
*/
|
|
245
|
+
static mul(a, b) {
|
|
246
|
+
return a.clone().mul(b);
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Returns a **new** instance equal to `a / b`.
|
|
250
|
+
*
|
|
251
|
+
* @throws `"Division by zero"` when `b` is zero.
|
|
252
|
+
*/
|
|
253
|
+
static div(a, b) {
|
|
254
|
+
return a.clone().div(b);
|
|
255
|
+
}
|
|
256
|
+
// ── Instance arithmetic (mutate this) ──────────────────────────────────────
|
|
257
|
+
/**
|
|
258
|
+
* Adds `other` to this number in-place.
|
|
259
|
+
*
|
|
260
|
+
* When the exponent difference exceeds `defaults.scaleCutoff`, the smaller
|
|
261
|
+
* operand has no effect and `this` is returned unchanged.
|
|
262
|
+
*
|
|
263
|
+
* **Mutates `this`. Returns `this`.**
|
|
158
264
|
*
|
|
159
265
|
* @example
|
|
160
|
-
*
|
|
266
|
+
* gold.add(drop); // gold is mutated
|
|
161
267
|
*/
|
|
162
268
|
add(other) {
|
|
163
|
-
if (this.coefficient === 0) return other;
|
|
164
269
|
if (other.coefficient === 0) return this;
|
|
270
|
+
if (this.coefficient === 0) {
|
|
271
|
+
this.coefficient = other.coefficient;
|
|
272
|
+
this.exponent = other.exponent;
|
|
273
|
+
return this;
|
|
274
|
+
}
|
|
275
|
+
const cutoff = _scaleCutoff;
|
|
165
276
|
const diff = this.exponent - other.exponent;
|
|
166
|
-
if (diff >
|
|
167
|
-
if (diff < -
|
|
168
|
-
|
|
169
|
-
|
|
277
|
+
if (diff > cutoff) return this;
|
|
278
|
+
if (diff < -cutoff) {
|
|
279
|
+
this.coefficient = other.coefficient;
|
|
280
|
+
this.exponent = other.exponent;
|
|
281
|
+
return this;
|
|
282
|
+
}
|
|
283
|
+
const oc = other.coefficient;
|
|
284
|
+
const oe = other.exponent;
|
|
285
|
+
let c, e;
|
|
170
286
|
if (diff >= 0) {
|
|
171
|
-
c = this.coefficient +
|
|
287
|
+
c = this.coefficient + oc / pow10(diff);
|
|
172
288
|
e = this.exponent;
|
|
173
289
|
} else {
|
|
174
|
-
c =
|
|
175
|
-
e =
|
|
290
|
+
c = oc + this.coefficient / pow10(-diff);
|
|
291
|
+
e = oe;
|
|
176
292
|
}
|
|
177
|
-
return
|
|
293
|
+
return this._normalizeInto(c, e);
|
|
178
294
|
}
|
|
179
295
|
/**
|
|
180
|
-
*
|
|
296
|
+
* Subtracts `other` from this number in-place.
|
|
181
297
|
*
|
|
182
|
-
*
|
|
183
|
-
* new ArbitraryNumber(3.5, 3).sub(new ArbitraryNumber(1.5, 3)); // 2*10^3
|
|
298
|
+
* **Mutates `this`. Returns `this`.**
|
|
184
299
|
*/
|
|
185
300
|
sub(other) {
|
|
186
301
|
if (other.coefficient === 0) return this;
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
const
|
|
190
|
-
if (
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
302
|
+
const oc = other.coefficient;
|
|
303
|
+
const oe = other.exponent;
|
|
304
|
+
const negOc = -oc;
|
|
305
|
+
if (this.coefficient === 0) {
|
|
306
|
+
this.coefficient = negOc;
|
|
307
|
+
this.exponent = oe;
|
|
308
|
+
return this;
|
|
309
|
+
}
|
|
310
|
+
const cutoff = _scaleCutoff;
|
|
311
|
+
const diff = this.exponent - oe;
|
|
312
|
+
if (diff > cutoff) return this;
|
|
313
|
+
if (diff < -cutoff) {
|
|
314
|
+
this.coefficient = negOc;
|
|
315
|
+
this.exponent = oe;
|
|
316
|
+
return this;
|
|
317
|
+
}
|
|
318
|
+
let c, e;
|
|
194
319
|
if (diff >= 0) {
|
|
195
|
-
c = this.coefficient +
|
|
320
|
+
c = this.coefficient + negOc / pow10(diff);
|
|
196
321
|
e = this.exponent;
|
|
197
322
|
} else {
|
|
198
|
-
c =
|
|
199
|
-
e =
|
|
323
|
+
c = negOc + this.coefficient / pow10(-diff);
|
|
324
|
+
e = oe;
|
|
200
325
|
}
|
|
201
|
-
return
|
|
326
|
+
return this._normalizeInto(c, e);
|
|
202
327
|
}
|
|
203
328
|
/**
|
|
204
|
-
*
|
|
329
|
+
* Multiplies this number by `other` in-place.
|
|
205
330
|
*
|
|
206
|
-
*
|
|
207
|
-
* new ArbitraryNumber(2, 3).mul(new ArbitraryNumber(3, 4)); // 6*10^7
|
|
331
|
+
* **Mutates `this`. Returns `this`.**
|
|
208
332
|
*/
|
|
209
333
|
mul(other) {
|
|
210
|
-
if (this.coefficient === 0
|
|
334
|
+
if (this.coefficient === 0) return this;
|
|
335
|
+
if (other.coefficient === 0) {
|
|
336
|
+
this.coefficient = 0;
|
|
337
|
+
this.exponent = 0;
|
|
338
|
+
return this;
|
|
339
|
+
}
|
|
211
340
|
const c = this.coefficient * other.coefficient;
|
|
212
341
|
const e = this.exponent + other.exponent;
|
|
213
342
|
const absC = c < 0 ? -c : c;
|
|
214
|
-
if (absC >= 10)
|
|
215
|
-
|
|
343
|
+
if (absC >= 10) {
|
|
344
|
+
this.coefficient = c / 10;
|
|
345
|
+
this.exponent = e + 1;
|
|
346
|
+
} else {
|
|
347
|
+
this.coefficient = c;
|
|
348
|
+
this.exponent = e;
|
|
349
|
+
}
|
|
350
|
+
return this;
|
|
216
351
|
}
|
|
217
352
|
/**
|
|
218
|
-
*
|
|
353
|
+
* Divides this number by `other` in-place.
|
|
219
354
|
*
|
|
220
|
-
*
|
|
221
|
-
* new ArbitraryNumber(6, 7).div(new ArbitraryNumber(3, 4)); // 2*10^3
|
|
355
|
+
* **Mutates `this`. Returns `this`.**
|
|
222
356
|
*
|
|
223
357
|
* @throws `"Division by zero"` when `other` is zero.
|
|
224
358
|
*/
|
|
225
359
|
div(other) {
|
|
226
|
-
if (other.coefficient === 0)
|
|
360
|
+
if (other.coefficient === 0) {
|
|
361
|
+
throw new ArbitraryNumberDomainError("Division by zero", { dividend: this.toNumber(), divisor: 0 });
|
|
362
|
+
}
|
|
363
|
+
if (this.coefficient === 0) return this;
|
|
227
364
|
const c = this.coefficient / other.coefficient;
|
|
228
365
|
const e = this.exponent - other.exponent;
|
|
229
|
-
if (c === 0)
|
|
366
|
+
if (c === 0) {
|
|
367
|
+
this.coefficient = 0;
|
|
368
|
+
this.exponent = 0;
|
|
369
|
+
return this;
|
|
370
|
+
}
|
|
230
371
|
const absC = c < 0 ? -c : c;
|
|
231
|
-
if (absC < 1)
|
|
232
|
-
|
|
372
|
+
if (absC < 1) {
|
|
373
|
+
this.coefficient = c * 10;
|
|
374
|
+
this.exponent = e - 1;
|
|
375
|
+
} else {
|
|
376
|
+
this.coefficient = c;
|
|
377
|
+
this.exponent = e;
|
|
378
|
+
}
|
|
379
|
+
return this;
|
|
233
380
|
}
|
|
234
381
|
/**
|
|
235
|
-
*
|
|
382
|
+
* Negates this number in-place.
|
|
236
383
|
*
|
|
237
|
-
*
|
|
238
|
-
* new ArbitraryNumber(1.5, 3).negate(); // -1.5*10^3
|
|
384
|
+
* **Mutates `this`. Returns `this`.**
|
|
239
385
|
*/
|
|
240
386
|
negate() {
|
|
241
|
-
|
|
242
|
-
return
|
|
387
|
+
this.coefficient = -this.coefficient;
|
|
388
|
+
return this;
|
|
243
389
|
}
|
|
244
390
|
/**
|
|
245
|
-
*
|
|
246
|
-
*
|
|
247
|
-
* Returns `this` unchanged when the number is already non-negative.
|
|
391
|
+
* Sets this number to its absolute value in-place.
|
|
248
392
|
*
|
|
249
|
-
*
|
|
250
|
-
* new ArbitraryNumber(-1.5, 3).abs(); // 1.5*10^3
|
|
393
|
+
* **Mutates `this`. Returns `this`.**
|
|
251
394
|
*/
|
|
252
395
|
abs() {
|
|
253
|
-
if (this.coefficient
|
|
254
|
-
return
|
|
396
|
+
if (this.coefficient < 0) this.coefficient = -this.coefficient;
|
|
397
|
+
return this;
|
|
255
398
|
}
|
|
256
399
|
/**
|
|
257
|
-
*
|
|
400
|
+
* Raises this number to the power `n` in-place.
|
|
258
401
|
*
|
|
259
402
|
* Supports integer, fractional, and negative exponents.
|
|
260
|
-
* `x^0` always
|
|
403
|
+
* `x^0` always sets `this` to `1`, including `0^0` (by convention).
|
|
261
404
|
*
|
|
262
|
-
*
|
|
263
|
-
* new ArbitraryNumber(2, 3).pow(2); // 4*10^6
|
|
264
|
-
* new ArbitraryNumber(2, 0).pow(-1); // 5*10^-1 (= 0.5)
|
|
405
|
+
* **Mutates `this`. Returns `this`.**
|
|
265
406
|
*
|
|
266
|
-
* @param n - The exponent to raise this number to.
|
|
267
407
|
* @throws `"Zero cannot be raised to a negative power"` when this is zero and `n < 0`.
|
|
268
408
|
*/
|
|
269
409
|
pow(n) {
|
|
270
410
|
if (n === 0) {
|
|
271
|
-
|
|
411
|
+
this.coefficient = 1;
|
|
412
|
+
this.exponent = 0;
|
|
413
|
+
return this;
|
|
272
414
|
}
|
|
273
415
|
if (this.coefficient === 0) {
|
|
274
416
|
if (n < 0) {
|
|
275
417
|
throw new ArbitraryNumberDomainError("Zero cannot be raised to a negative power", { exponent: n });
|
|
276
418
|
}
|
|
277
|
-
return
|
|
419
|
+
return this;
|
|
278
420
|
}
|
|
279
421
|
if (this.coefficient < 0 && !Number.isInteger(n)) {
|
|
280
|
-
throw new ArbitraryNumberDomainError(
|
|
422
|
+
throw new ArbitraryNumberDomainError(
|
|
423
|
+
"ArbitraryNumber.pow: fractional exponent of a negative base is not supported",
|
|
424
|
+
{ base: this.toNumber(), exponent: n }
|
|
425
|
+
);
|
|
281
426
|
}
|
|
282
427
|
const rawExp = this.exponent * n;
|
|
283
428
|
const intExp = Math.floor(rawExp);
|
|
284
429
|
const fracExp = rawExp - intExp;
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
intExp
|
|
288
|
-
);
|
|
430
|
+
const newC = Math.pow(this.coefficient, n) * pow10(fracExp);
|
|
431
|
+
return this._normalizeInto(newC, intExp);
|
|
289
432
|
}
|
|
290
433
|
/**
|
|
291
|
-
* Fused multiply-add: `(this * multiplier) + addend`.
|
|
292
|
-
*
|
|
293
|
-
* Faster than `.mul(multiplier).add(addend)` because it avoids allocating an
|
|
294
|
-
* intermediate ArbitraryNumber for the product. One normalisation pass total.
|
|
434
|
+
* Fused multiply-add in-place: `this = (this * multiplier) + addend`.
|
|
295
435
|
*
|
|
296
|
-
*
|
|
436
|
+
* Faster than `.mul(multiplier).add(addend)` — one normalisation pass total, no
|
|
437
|
+
* intermediate allocation.
|
|
297
438
|
*
|
|
298
|
-
*
|
|
299
|
-
* // Equivalent to value.mul(mult).add(boost) but ~35-50% faster
|
|
300
|
-
* const prestiged = currentValue.mulAdd(multiplier, boost);
|
|
439
|
+
* **Mutates `this`. Returns `this`.**
|
|
301
440
|
*/
|
|
302
441
|
mulAdd(multiplier, addend) {
|
|
303
|
-
if (this.coefficient === 0 || multiplier.coefficient === 0)
|
|
442
|
+
if (this.coefficient === 0 || multiplier.coefficient === 0) {
|
|
443
|
+
this.coefficient = addend.coefficient;
|
|
444
|
+
this.exponent = addend.exponent;
|
|
445
|
+
return this;
|
|
446
|
+
}
|
|
447
|
+
const ac = addend.coefficient;
|
|
448
|
+
const ae = addend.exponent;
|
|
304
449
|
let cp = this.coefficient * multiplier.coefficient;
|
|
305
450
|
let ep = this.exponent + multiplier.exponent;
|
|
306
451
|
const absCp = cp < 0 ? -cp : cp;
|
|
@@ -308,87 +453,144 @@ var _ArbitraryNumber = class _ArbitraryNumber {
|
|
|
308
453
|
cp /= 10;
|
|
309
454
|
ep += 1;
|
|
310
455
|
}
|
|
311
|
-
if (
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
456
|
+
if (ac === 0) {
|
|
457
|
+
this.coefficient = cp;
|
|
458
|
+
this.exponent = ep;
|
|
459
|
+
return this;
|
|
460
|
+
}
|
|
461
|
+
const cutoff = _scaleCutoff;
|
|
462
|
+
const diff = ep - ae;
|
|
463
|
+
if (diff > cutoff) {
|
|
464
|
+
this.coefficient = cp;
|
|
465
|
+
this.exponent = ep;
|
|
466
|
+
return this;
|
|
467
|
+
}
|
|
468
|
+
if (diff < -cutoff) {
|
|
469
|
+
this.coefficient = ac;
|
|
470
|
+
this.exponent = ae;
|
|
471
|
+
return this;
|
|
472
|
+
}
|
|
315
473
|
let c, e;
|
|
316
474
|
if (diff >= 0) {
|
|
317
|
-
c = cp +
|
|
475
|
+
c = cp + ac / pow10(diff);
|
|
318
476
|
e = ep;
|
|
319
477
|
} else {
|
|
320
|
-
c =
|
|
321
|
-
e =
|
|
478
|
+
c = ac + cp / pow10(-diff);
|
|
479
|
+
e = ae;
|
|
322
480
|
}
|
|
323
|
-
return
|
|
481
|
+
return this._normalizeInto(c, e);
|
|
324
482
|
}
|
|
325
483
|
/**
|
|
326
|
-
*
|
|
327
|
-
*
|
|
328
|
-
*
|
|
329
|
-
*
|
|
330
|
-
*
|
|
331
|
-
*
|
|
332
|
-
*
|
|
333
|
-
*
|
|
334
|
-
* // Equivalent to base.add(bonus).mul(multiplier) but ~20-25% faster
|
|
335
|
-
* const upgraded = baseValue.addMul(bonus, multiplier);
|
|
484
|
+
* @internal
|
|
485
|
+
* Computes `(this + sign * addendC * 10^addendE)` and writes the normalised
|
|
486
|
+
* result back into `this`. Used by `addMul` and `subMul` to share the alignment
|
|
487
|
+
* logic without duplication.
|
|
488
|
+
*
|
|
489
|
+
* Returns the sign-adjusted addend coefficient so callers can detect the
|
|
490
|
+
* zero-result case. Writes the intermediate normalised sum into `this.coefficient`
|
|
491
|
+
* / `this.exponent` ready for the subsequent multiply step.
|
|
336
492
|
*/
|
|
337
|
-
|
|
338
|
-
if (multiplier.coefficient === 0) return _ArbitraryNumber.Zero;
|
|
339
|
-
let cs, es;
|
|
340
|
-
if (this.coefficient === 0 && addend.coefficient === 0) return _ArbitraryNumber.Zero;
|
|
493
|
+
_addScaledInto(addendC, addendE) {
|
|
341
494
|
if (this.coefficient === 0) {
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
495
|
+
this.coefficient = addendC;
|
|
496
|
+
this.exponent = addendE;
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
if (addendC === 0) return;
|
|
500
|
+
const cutoff = _scaleCutoff;
|
|
501
|
+
const diff = this.exponent - addendE;
|
|
502
|
+
if (diff > cutoff) return;
|
|
503
|
+
if (diff < -cutoff) {
|
|
504
|
+
this.coefficient = addendC;
|
|
505
|
+
this.exponent = addendE;
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
let cs, es;
|
|
509
|
+
if (diff >= 0) {
|
|
510
|
+
cs = this.coefficient + addendC / pow10(diff);
|
|
346
511
|
es = this.exponent;
|
|
347
512
|
} else {
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
}
|
|
363
|
-
if (cs === 0) return _ArbitraryNumber.Zero;
|
|
364
|
-
const abs = Math.abs(cs);
|
|
365
|
-
const shift = Math.floor(Math.log10(abs));
|
|
366
|
-
const scale = pow10(shift);
|
|
367
|
-
if (scale === 0) return _ArbitraryNumber.Zero;
|
|
368
|
-
cs /= scale;
|
|
369
|
-
es += shift;
|
|
513
|
+
cs = addendC + this.coefficient / pow10(-diff);
|
|
514
|
+
es = addendE;
|
|
515
|
+
}
|
|
516
|
+
if (cs === 0) {
|
|
517
|
+
this.coefficient = 0;
|
|
518
|
+
this.exponent = 0;
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
const abs = cs < 0 ? -cs : cs;
|
|
522
|
+
if (abs < 10) {
|
|
523
|
+
if (abs >= 1) {
|
|
524
|
+
this.coefficient = cs;
|
|
525
|
+
this.exponent = es;
|
|
526
|
+
return;
|
|
370
527
|
}
|
|
528
|
+
this.coefficient = cs * 10;
|
|
529
|
+
this.exponent = es - 1;
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
if (abs < 100) {
|
|
533
|
+
this.coefficient = cs / 10;
|
|
534
|
+
this.exponent = es + 1;
|
|
535
|
+
return;
|
|
371
536
|
}
|
|
372
|
-
|
|
373
|
-
|
|
537
|
+
if (!isFinite(cs)) {
|
|
538
|
+
this.coefficient = 0;
|
|
539
|
+
this.exponent = 0;
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
const shift = Math.floor(Math.log10(abs));
|
|
543
|
+
const scale = pow10(shift);
|
|
544
|
+
if (scale === 0) {
|
|
545
|
+
this.coefficient = 0;
|
|
546
|
+
this.exponent = 0;
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
this.coefficient = cs / scale;
|
|
550
|
+
this.exponent = es + shift;
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* Fused add-multiply in-place: `this = (this + addend) * multiplier`.
|
|
554
|
+
*
|
|
555
|
+
* **Mutates `this`. Returns `this`.**
|
|
556
|
+
*/
|
|
557
|
+
addMul(addend, multiplier) {
|
|
558
|
+
if (multiplier.coefficient === 0) {
|
|
559
|
+
this.coefficient = 0;
|
|
560
|
+
this.exponent = 0;
|
|
561
|
+
return this;
|
|
562
|
+
}
|
|
563
|
+
const ac = addend.coefficient;
|
|
564
|
+
const ae = addend.exponent;
|
|
565
|
+
const mc = multiplier.coefficient;
|
|
566
|
+
const me = multiplier.exponent;
|
|
567
|
+
this._addScaledInto(ac, ae);
|
|
568
|
+
if (this.coefficient === 0) return this;
|
|
569
|
+
let cp = this.coefficient * mc;
|
|
570
|
+
const ep = this.exponent + me;
|
|
374
571
|
const absCp = cp < 0 ? -cp : cp;
|
|
375
572
|
if (absCp >= 10) {
|
|
376
573
|
cp /= 10;
|
|
377
|
-
|
|
574
|
+
this.coefficient = cp;
|
|
575
|
+
this.exponent = ep + 1;
|
|
576
|
+
return this;
|
|
378
577
|
}
|
|
379
|
-
|
|
578
|
+
this.coefficient = cp;
|
|
579
|
+
this.exponent = ep;
|
|
580
|
+
return this;
|
|
380
581
|
}
|
|
381
582
|
/**
|
|
382
|
-
* Fused multiply-subtract: `(this * multiplier) - subtrahend`.
|
|
383
|
-
*
|
|
384
|
-
* Avoids one intermediate allocation vs `.mul(multiplier).sub(subtrahend)`.
|
|
583
|
+
* Fused multiply-subtract in-place: `this = (this * multiplier) - subtrahend`.
|
|
385
584
|
*
|
|
386
|
-
*
|
|
585
|
+
* **Mutates `this`. Returns `this`.**
|
|
387
586
|
*/
|
|
388
587
|
mulSub(multiplier, subtrahend) {
|
|
588
|
+
const sc = subtrahend.coefficient;
|
|
589
|
+
const se = subtrahend.exponent;
|
|
389
590
|
if (this.coefficient === 0 || multiplier.coefficient === 0) {
|
|
390
|
-
|
|
391
|
-
|
|
591
|
+
this.coefficient = sc === 0 ? 0 : -sc;
|
|
592
|
+
this.exponent = sc === 0 ? 0 : se;
|
|
593
|
+
return this;
|
|
392
594
|
}
|
|
393
595
|
let cp = this.coefficient * multiplier.coefficient;
|
|
394
596
|
let ep = this.exponent + multiplier.exponent;
|
|
@@ -397,85 +599,81 @@ var _ArbitraryNumber = class _ArbitraryNumber {
|
|
|
397
599
|
cp /= 10;
|
|
398
600
|
ep += 1;
|
|
399
601
|
}
|
|
400
|
-
if (
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
602
|
+
if (sc === 0) {
|
|
603
|
+
this.coefficient = cp;
|
|
604
|
+
this.exponent = ep;
|
|
605
|
+
return this;
|
|
606
|
+
}
|
|
607
|
+
const cutoff = _scaleCutoff;
|
|
608
|
+
const diff = ep - se;
|
|
609
|
+
if (diff > cutoff) {
|
|
610
|
+
this.coefficient = cp;
|
|
611
|
+
this.exponent = ep;
|
|
612
|
+
return this;
|
|
613
|
+
}
|
|
614
|
+
if (diff < -cutoff) {
|
|
615
|
+
this.coefficient = -sc;
|
|
616
|
+
this.exponent = se;
|
|
617
|
+
return this;
|
|
405
618
|
}
|
|
406
619
|
let c, e;
|
|
407
620
|
if (diff >= 0) {
|
|
408
|
-
c = cp -
|
|
621
|
+
c = cp - sc / pow10(diff);
|
|
409
622
|
e = ep;
|
|
410
623
|
} else {
|
|
411
|
-
c = -
|
|
412
|
-
e =
|
|
624
|
+
c = -sc + cp / pow10(-diff);
|
|
625
|
+
e = se;
|
|
413
626
|
}
|
|
414
|
-
return
|
|
627
|
+
return this._normalizeInto(c, e);
|
|
415
628
|
}
|
|
416
629
|
/**
|
|
417
|
-
* Fused subtract-multiply: `(this - subtrahend) * multiplier`.
|
|
418
|
-
*
|
|
419
|
-
* Avoids one intermediate allocation vs `.sub(subtrahend).mul(multiplier)`.
|
|
630
|
+
* Fused subtract-multiply in-place: `this = (this - subtrahend) * multiplier`.
|
|
420
631
|
*
|
|
421
|
-
*
|
|
632
|
+
* **Mutates `this`. Returns `this`.**
|
|
422
633
|
*/
|
|
423
634
|
subMul(subtrahend, multiplier) {
|
|
424
|
-
if (multiplier.coefficient === 0)
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
cs = -subtrahend.coefficient;
|
|
429
|
-
es = subtrahend.exponent;
|
|
430
|
-
} else if (subtrahend.coefficient === 0) {
|
|
431
|
-
cs = this.coefficient;
|
|
432
|
-
es = this.exponent;
|
|
433
|
-
} else {
|
|
434
|
-
const diff = this.exponent - subtrahend.exponent;
|
|
435
|
-
if (diff > _ArbitraryNumber.PrecisionCutoff) {
|
|
436
|
-
cs = this.coefficient;
|
|
437
|
-
es = this.exponent;
|
|
438
|
-
} else if (diff < -_ArbitraryNumber.PrecisionCutoff) {
|
|
439
|
-
cs = -subtrahend.coefficient;
|
|
440
|
-
es = subtrahend.exponent;
|
|
441
|
-
} else {
|
|
442
|
-
if (diff >= 0) {
|
|
443
|
-
cs = this.coefficient - subtrahend.coefficient / pow10(diff);
|
|
444
|
-
es = this.exponent;
|
|
445
|
-
} else {
|
|
446
|
-
cs = -subtrahend.coefficient + this.coefficient / pow10(-diff);
|
|
447
|
-
es = subtrahend.exponent;
|
|
448
|
-
}
|
|
449
|
-
if (cs === 0) return _ArbitraryNumber.Zero;
|
|
450
|
-
const abs = Math.abs(cs);
|
|
451
|
-
const shift = Math.floor(Math.log10(abs));
|
|
452
|
-
const scale = pow10(shift);
|
|
453
|
-
if (scale === 0) return _ArbitraryNumber.Zero;
|
|
454
|
-
cs /= scale;
|
|
455
|
-
es += shift;
|
|
456
|
-
}
|
|
635
|
+
if (multiplier.coefficient === 0) {
|
|
636
|
+
this.coefficient = 0;
|
|
637
|
+
this.exponent = 0;
|
|
638
|
+
return this;
|
|
457
639
|
}
|
|
458
|
-
|
|
459
|
-
const
|
|
640
|
+
const sc = subtrahend.coefficient;
|
|
641
|
+
const se = subtrahend.exponent;
|
|
642
|
+
const mc = multiplier.coefficient;
|
|
643
|
+
const me = multiplier.exponent;
|
|
644
|
+
this._addScaledInto(-sc, se);
|
|
645
|
+
if (this.coefficient === 0) return this;
|
|
646
|
+
let cp = this.coefficient * mc;
|
|
647
|
+
const ep = this.exponent + me;
|
|
460
648
|
const absCp = cp < 0 ? -cp : cp;
|
|
461
649
|
if (absCp >= 10) {
|
|
462
650
|
cp /= 10;
|
|
463
|
-
|
|
651
|
+
this.coefficient = cp;
|
|
652
|
+
this.exponent = ep + 1;
|
|
653
|
+
return this;
|
|
464
654
|
}
|
|
465
|
-
|
|
655
|
+
this.coefficient = cp;
|
|
656
|
+
this.exponent = ep;
|
|
657
|
+
return this;
|
|
466
658
|
}
|
|
467
659
|
/**
|
|
468
|
-
* Fused divide-add: `(this / divisor) + addend`.
|
|
660
|
+
* Fused divide-add in-place: `this = (this / divisor) + addend`.
|
|
469
661
|
*
|
|
470
|
-
*
|
|
471
|
-
*
|
|
472
|
-
* Common pattern - efficiency bonus: `damage.divAdd(armor, flat)`
|
|
662
|
+
* **Mutates `this`. Returns `this`.**
|
|
473
663
|
*
|
|
474
664
|
* @throws `"Division by zero"` when divisor is zero.
|
|
475
665
|
*/
|
|
476
666
|
divAdd(divisor, addend) {
|
|
477
|
-
if (divisor.coefficient === 0)
|
|
478
|
-
|
|
667
|
+
if (divisor.coefficient === 0) {
|
|
668
|
+
throw new ArbitraryNumberDomainError("Division by zero", { dividend: this.toNumber(), divisor: 0 });
|
|
669
|
+
}
|
|
670
|
+
const ac = addend.coefficient;
|
|
671
|
+
const ae = addend.exponent;
|
|
672
|
+
if (this.coefficient === 0) {
|
|
673
|
+
this.coefficient = ac;
|
|
674
|
+
this.exponent = ae;
|
|
675
|
+
return this;
|
|
676
|
+
}
|
|
479
677
|
let cd = this.coefficient / divisor.coefficient;
|
|
480
678
|
let ed = this.exponent - divisor.exponent;
|
|
481
679
|
const absC = cd < 0 ? -cd : cd;
|
|
@@ -483,69 +681,318 @@ var _ArbitraryNumber = class _ArbitraryNumber {
|
|
|
483
681
|
cd *= 10;
|
|
484
682
|
ed -= 1;
|
|
485
683
|
}
|
|
486
|
-
if (
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
684
|
+
if (ac === 0) {
|
|
685
|
+
this.coefficient = cd;
|
|
686
|
+
this.exponent = ed;
|
|
687
|
+
return this;
|
|
688
|
+
}
|
|
689
|
+
const cutoff = _scaleCutoff;
|
|
690
|
+
const diff = ed - ae;
|
|
691
|
+
if (diff > cutoff) {
|
|
692
|
+
this.coefficient = cd;
|
|
693
|
+
this.exponent = ed;
|
|
694
|
+
return this;
|
|
695
|
+
}
|
|
696
|
+
if (diff < -cutoff) {
|
|
697
|
+
this.coefficient = ac;
|
|
698
|
+
this.exponent = ae;
|
|
699
|
+
return this;
|
|
700
|
+
}
|
|
490
701
|
let c, e;
|
|
491
702
|
if (diff >= 0) {
|
|
492
|
-
c = cd +
|
|
703
|
+
c = cd + ac / pow10(diff);
|
|
493
704
|
e = ed;
|
|
494
705
|
} else {
|
|
495
|
-
c =
|
|
496
|
-
e =
|
|
706
|
+
c = ac + cd / pow10(-diff);
|
|
707
|
+
e = ae;
|
|
497
708
|
}
|
|
498
|
-
return
|
|
709
|
+
return this._normalizeInto(c, e);
|
|
499
710
|
}
|
|
500
711
|
/**
|
|
501
|
-
*
|
|
712
|
+
* Fused multiply-divide in-place: `this = (this * multiplier) / divisor`.
|
|
713
|
+
*
|
|
714
|
+
* **Mutates `this`. Returns `this`.**
|
|
502
715
|
*
|
|
503
|
-
*
|
|
504
|
-
|
|
505
|
-
|
|
716
|
+
* @throws `"Division by zero"` when divisor is zero.
|
|
717
|
+
*/
|
|
718
|
+
mulDiv(multiplier, divisor) {
|
|
719
|
+
if (divisor.coefficient === 0) {
|
|
720
|
+
throw new ArbitraryNumberDomainError("Division by zero", { dividend: this.toNumber(), divisor: 0 });
|
|
721
|
+
}
|
|
722
|
+
if (this.coefficient === 0) return this;
|
|
723
|
+
if (multiplier.coefficient === 0) {
|
|
724
|
+
this.coefficient = 0;
|
|
725
|
+
this.exponent = 0;
|
|
726
|
+
return this;
|
|
727
|
+
}
|
|
728
|
+
const c = this.coefficient * multiplier.coefficient / divisor.coefficient;
|
|
729
|
+
const e = this.exponent + multiplier.exponent - divisor.exponent;
|
|
730
|
+
const absC = c < 0 ? -c : c;
|
|
731
|
+
if (absC >= 10) {
|
|
732
|
+
this.coefficient = c / 10;
|
|
733
|
+
this.exponent = e + 1;
|
|
734
|
+
} else if (absC < 1) {
|
|
735
|
+
this.coefficient = c * 10;
|
|
736
|
+
this.exponent = e - 1;
|
|
737
|
+
} else {
|
|
738
|
+
this.coefficient = c;
|
|
739
|
+
this.exponent = e;
|
|
740
|
+
}
|
|
741
|
+
return this;
|
|
742
|
+
}
|
|
743
|
+
// ── Batch (static, allocate) ───────────────────────────────────────────────
|
|
744
|
+
/**
|
|
745
|
+
* Efficiently sums an array of `ArbitraryNumber`s in a single pass.
|
|
506
746
|
*
|
|
507
|
-
*
|
|
508
|
-
*
|
|
747
|
+
* Maintains a running pivot exponent and rescales the accumulator when a larger
|
|
748
|
+
* exponent is encountered — one pass, no pre-scan needed.
|
|
509
749
|
*
|
|
510
|
-
*
|
|
750
|
+
* Empty array returns `Zero`. Single element returned as-is (no clone).
|
|
751
|
+
*/
|
|
752
|
+
static sumArray(numbers) {
|
|
753
|
+
const len = numbers.length;
|
|
754
|
+
if (len === 0) return _ArbitraryNumber.Zero;
|
|
755
|
+
if (len === 1) return numbers[0];
|
|
756
|
+
const cutoff = _scaleCutoff;
|
|
757
|
+
let pivot = -Infinity;
|
|
758
|
+
let total = 0;
|
|
759
|
+
for (let i = 0; i < len; i++) {
|
|
760
|
+
const n = numbers[i];
|
|
761
|
+
if (n.coefficient === 0) continue;
|
|
762
|
+
if (n.exponent > pivot) {
|
|
763
|
+
if (pivot !== -Infinity) {
|
|
764
|
+
const drop = n.exponent - pivot;
|
|
765
|
+
total = drop > cutoff ? 0 : total / pow10(drop);
|
|
766
|
+
}
|
|
767
|
+
pivot = n.exponent;
|
|
768
|
+
total += n.coefficient;
|
|
769
|
+
} else {
|
|
770
|
+
const diff = pivot - n.exponent;
|
|
771
|
+
if (diff <= cutoff) total += n.coefficient / pow10(diff);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
if (total === 0 || pivot === -Infinity) return _ArbitraryNumber.Zero;
|
|
775
|
+
const abs = total < 0 ? -total : total;
|
|
776
|
+
if (abs < 10) {
|
|
777
|
+
if (abs >= 1) return _ArbitraryNumber.unsafe(total, pivot);
|
|
778
|
+
return _ArbitraryNumber.unsafe(total * 10, pivot - 1);
|
|
779
|
+
}
|
|
780
|
+
if (abs < 100) return _ArbitraryNumber.unsafe(total / 10, pivot + 1);
|
|
781
|
+
const shift = Math.floor(Math.log10(abs));
|
|
782
|
+
const scale = pow10(shift);
|
|
783
|
+
if (scale === 0) return _ArbitraryNumber.Zero;
|
|
784
|
+
return _ArbitraryNumber.unsafe(total / scale, pivot + shift);
|
|
785
|
+
}
|
|
786
|
+
/**
|
|
787
|
+
* Multiplies an array of `ArbitraryNumber`s in a single pass.
|
|
511
788
|
*
|
|
512
|
-
*
|
|
513
|
-
|
|
789
|
+
* Empty array returns `One`. Single element returned as-is.
|
|
790
|
+
*/
|
|
791
|
+
static productArray(numbers) {
|
|
792
|
+
const len = numbers.length;
|
|
793
|
+
if (len === 0) return _ArbitraryNumber.One;
|
|
794
|
+
if (len === 1) return numbers[0];
|
|
795
|
+
let c = 1;
|
|
796
|
+
let e = 0;
|
|
797
|
+
for (let i = 0; i < len; i++) {
|
|
798
|
+
const n = numbers[i];
|
|
799
|
+
if (n.coefficient === 0) return _ArbitraryNumber.Zero;
|
|
800
|
+
c *= n.coefficient;
|
|
801
|
+
e += n.exponent;
|
|
802
|
+
if (c >= 10 || c <= -10) {
|
|
803
|
+
c /= 10;
|
|
804
|
+
e += 1;
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
return _ArbitraryNumber._normalizeNew(c, e);
|
|
808
|
+
}
|
|
809
|
+
/**
|
|
810
|
+
* Returns the largest value in an array. Empty array returns `Zero`.
|
|
811
|
+
*/
|
|
812
|
+
static maxOfArray(numbers) {
|
|
813
|
+
if (numbers.length === 0) return _ArbitraryNumber.Zero;
|
|
814
|
+
let max = numbers[0];
|
|
815
|
+
for (let i = 1; i < numbers.length; i++) {
|
|
816
|
+
if (numbers[i].greaterThan(max)) max = numbers[i];
|
|
817
|
+
}
|
|
818
|
+
return max;
|
|
819
|
+
}
|
|
820
|
+
/**
|
|
821
|
+
* Returns the smallest value in an array. Empty array returns `Zero`.
|
|
822
|
+
*/
|
|
823
|
+
static minOfArray(numbers) {
|
|
824
|
+
if (numbers.length === 0) return _ArbitraryNumber.Zero;
|
|
825
|
+
let min = numbers[0];
|
|
826
|
+
for (let i = 1; i < numbers.length; i++) {
|
|
827
|
+
if (numbers[i].lessThan(min)) min = numbers[i];
|
|
828
|
+
}
|
|
829
|
+
return min;
|
|
830
|
+
}
|
|
831
|
+
// ── Rounding (mutate) ──────────────────────────────────────────────────────
|
|
832
|
+
/**
|
|
833
|
+
* Rounds down to the nearest integer in-place (floor toward −∞).
|
|
834
|
+
*
|
|
835
|
+
* **Mutates `this`. Returns `this`.**
|
|
836
|
+
*/
|
|
837
|
+
floor() {
|
|
838
|
+
if (this.coefficient === 0) return this;
|
|
839
|
+
if (this.exponent >= _scaleCutoff) return this;
|
|
840
|
+
if (this.exponent < 0) {
|
|
841
|
+
this.coefficient = this.coefficient >= 0 ? 0 : -1;
|
|
842
|
+
this.exponent = 0;
|
|
843
|
+
return this;
|
|
844
|
+
}
|
|
845
|
+
return this._normalizeInto(Math.floor(this.coefficient * pow10(this.exponent)), 0);
|
|
846
|
+
}
|
|
847
|
+
/**
|
|
848
|
+
* Rounds up to the nearest integer in-place (ceil toward +∞).
|
|
849
|
+
*
|
|
850
|
+
* **Mutates `this`. Returns `this`.**
|
|
851
|
+
*/
|
|
852
|
+
ceil() {
|
|
853
|
+
if (this.coefficient === 0) return this;
|
|
854
|
+
if (this.exponent >= _scaleCutoff) return this;
|
|
855
|
+
if (this.exponent < 0) {
|
|
856
|
+
const ceiled = this.coefficient > 0 ? 1 : 0;
|
|
857
|
+
this.coefficient = ceiled;
|
|
858
|
+
this.exponent = 0;
|
|
859
|
+
return this;
|
|
860
|
+
}
|
|
861
|
+
return this._normalizeInto(Math.ceil(this.coefficient * pow10(this.exponent)), 0);
|
|
862
|
+
}
|
|
863
|
+
/**
|
|
864
|
+
* Rounds to the nearest integer in-place.
|
|
865
|
+
*
|
|
866
|
+
* Uses `Math.round` semantics: half-values round toward positive infinity
|
|
867
|
+
* (`0.5 → 1`, `-0.5 → 0`). This matches JavaScript's built-in convention.
|
|
868
|
+
*
|
|
869
|
+
* **Mutates `this`. Returns `this`.**
|
|
870
|
+
*/
|
|
871
|
+
round() {
|
|
872
|
+
if (this.coefficient === 0) return this;
|
|
873
|
+
if (this.exponent >= _scaleCutoff) return this;
|
|
874
|
+
if (this.exponent < 0) {
|
|
875
|
+
if (this.exponent <= -2) {
|
|
876
|
+
this.coefficient = 0;
|
|
877
|
+
this.exponent = 0;
|
|
878
|
+
return this;
|
|
879
|
+
}
|
|
880
|
+
const rounded = Math.round(this.coefficient * 0.1) || 0;
|
|
881
|
+
this.coefficient = rounded;
|
|
882
|
+
this.exponent = 0;
|
|
883
|
+
return this;
|
|
884
|
+
}
|
|
885
|
+
return this._normalizeInto(Math.round(this.coefficient * pow10(this.exponent)), 0);
|
|
886
|
+
}
|
|
887
|
+
/**
|
|
888
|
+
* Truncates toward zero in-place.
|
|
889
|
+
*
|
|
890
|
+
* **Mutates `this`. Returns `this`.**
|
|
891
|
+
*/
|
|
892
|
+
trunc() {
|
|
893
|
+
if (this.coefficient === 0) return this;
|
|
894
|
+
if (this.exponent >= _scaleCutoff) return this;
|
|
895
|
+
if (this.exponent < 0) {
|
|
896
|
+
this.coefficient = 0;
|
|
897
|
+
this.exponent = 0;
|
|
898
|
+
return this;
|
|
899
|
+
}
|
|
900
|
+
return this._normalizeInto(Math.trunc(this.coefficient * pow10(this.exponent)), 0);
|
|
901
|
+
}
|
|
902
|
+
// ── Roots & logs ───────────────────────────────────────────────────────────
|
|
903
|
+
/**
|
|
904
|
+
* Returns √this in-place.
|
|
905
|
+
*
|
|
906
|
+
* **Mutates `this`. Returns `this`.**
|
|
907
|
+
*
|
|
908
|
+
* @throws `"Square root of negative number"` when this is negative.
|
|
909
|
+
*/
|
|
910
|
+
sqrt() {
|
|
911
|
+
if (this.coefficient < 0) {
|
|
912
|
+
throw new ArbitraryNumberDomainError("Square root of negative number", { value: this.toNumber() });
|
|
913
|
+
}
|
|
914
|
+
if (this.coefficient === 0) return this;
|
|
915
|
+
if (this.exponent % 2 !== 0) {
|
|
916
|
+
this.coefficient = Math.sqrt(this.coefficient * 10);
|
|
917
|
+
this.exponent = (this.exponent - 1) / 2;
|
|
918
|
+
} else {
|
|
919
|
+
this.coefficient = Math.sqrt(this.coefficient);
|
|
920
|
+
this.exponent = this.exponent / 2;
|
|
921
|
+
}
|
|
922
|
+
return this;
|
|
923
|
+
}
|
|
924
|
+
/**
|
|
925
|
+
* Returns ∛this in-place.
|
|
926
|
+
*
|
|
927
|
+
* **Mutates `this`. Returns `this`.**
|
|
928
|
+
*/
|
|
929
|
+
cbrt() {
|
|
930
|
+
if (this.coefficient === 0) return this;
|
|
931
|
+
const rem = (this.exponent % 3 + 3) % 3;
|
|
932
|
+
const baseExp = (this.exponent - rem) / 3;
|
|
933
|
+
if (rem === 0) {
|
|
934
|
+
this.coefficient = Math.cbrt(this.coefficient);
|
|
935
|
+
this.exponent = baseExp;
|
|
936
|
+
} else if (rem === 1) {
|
|
937
|
+
this.coefficient = Math.cbrt(this.coefficient * 10);
|
|
938
|
+
this.exponent = baseExp;
|
|
939
|
+
} else {
|
|
940
|
+
this.coefficient = Math.cbrt(this.coefficient * 100);
|
|
941
|
+
this.exponent = baseExp;
|
|
942
|
+
}
|
|
943
|
+
return this;
|
|
944
|
+
}
|
|
945
|
+
/**
|
|
946
|
+
* Returns `log10(this)` as a plain `number`.
|
|
947
|
+
*
|
|
948
|
+
* @throws `"Logarithm of zero is undefined"` when this is zero.
|
|
949
|
+
* @throws `"Logarithm of a negative number is undefined"` when this is negative.
|
|
950
|
+
*/
|
|
951
|
+
log10() {
|
|
952
|
+
if (this.coefficient === 0) {
|
|
953
|
+
throw new ArbitraryNumberDomainError("Logarithm of zero is undefined", { value: 0 });
|
|
954
|
+
}
|
|
955
|
+
if (this.coefficient < 0) {
|
|
956
|
+
throw new ArbitraryNumberDomainError("Logarithm of a negative number is undefined", { value: this.toNumber() });
|
|
957
|
+
}
|
|
958
|
+
return Math.log10(this.coefficient) + this.exponent;
|
|
959
|
+
}
|
|
960
|
+
/**
|
|
961
|
+
* Returns `log_base(this)` as a plain `number`.
|
|
962
|
+
*
|
|
963
|
+
* @param base - Must be positive and not 1.
|
|
964
|
+
* @throws `"Logarithm base must be positive and not 1"` for invalid base.
|
|
965
|
+
*/
|
|
966
|
+
log(base) {
|
|
967
|
+
if (base <= 0 || base === 1 || !isFinite(base)) {
|
|
968
|
+
throw new ArbitraryNumberDomainError("Logarithm base must be positive and not 1", { base });
|
|
969
|
+
}
|
|
970
|
+
return this.log10() / Math.log10(base);
|
|
971
|
+
}
|
|
972
|
+
/**
|
|
973
|
+
* Returns `ln(this)` as a plain `number`.
|
|
974
|
+
*/
|
|
975
|
+
ln() {
|
|
976
|
+
return this.log10() / Math.LOG10E;
|
|
977
|
+
}
|
|
978
|
+
/**
|
|
979
|
+
* Returns `10^n` as a new `ArbitraryNumber`.
|
|
514
980
|
*
|
|
515
|
-
* @
|
|
981
|
+
* @throws `"ArbitraryNumber.exp10: n must be finite"` for non-finite `n`.
|
|
516
982
|
*/
|
|
517
|
-
static
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
if (len === 1) return numbers[0];
|
|
521
|
-
let pivotExp = numbers[0].exponent;
|
|
522
|
-
for (let i = 1; i < len; i++) {
|
|
523
|
-
const n = numbers[i];
|
|
524
|
-
if (n.exponent > pivotExp) pivotExp = n.exponent;
|
|
525
|
-
}
|
|
526
|
-
let total = 0;
|
|
527
|
-
for (let i = 0; i < len; i++) {
|
|
528
|
-
const n = numbers[i];
|
|
529
|
-
if (n.coefficient === 0) continue;
|
|
530
|
-
const diff = pivotExp - n.exponent;
|
|
531
|
-
if (diff > _ArbitraryNumber.PrecisionCutoff) continue;
|
|
532
|
-
total += n.coefficient / pow10(diff);
|
|
983
|
+
static exp10(n) {
|
|
984
|
+
if (!isFinite(n)) {
|
|
985
|
+
throw new ArbitraryNumberInputError("ArbitraryNumber.exp10: n must be finite", n);
|
|
533
986
|
}
|
|
534
|
-
|
|
535
|
-
const
|
|
536
|
-
|
|
537
|
-
const scale = pow10(shift);
|
|
538
|
-
if (scale === 0) return _ArbitraryNumber.Zero;
|
|
539
|
-
return _ArbitraryNumber.createNormalized(total / scale, pivotExp + shift);
|
|
987
|
+
const intPart = Math.floor(n);
|
|
988
|
+
const fracPart = n - intPart;
|
|
989
|
+
return _ArbitraryNumber.unsafe(Math.pow(10, fracPart), intPart);
|
|
540
990
|
}
|
|
991
|
+
// ── Comparisons (read-only) ────────────────────────────────────────────────
|
|
541
992
|
/**
|
|
542
993
|
* Compares this number to `other`.
|
|
543
994
|
*
|
|
544
995
|
* @returns `1` if `this > other`, `-1` if `this < other`, `0` if equal.
|
|
545
|
-
*
|
|
546
|
-
* @example
|
|
547
|
-
* new ArbitraryNumber(1, 4).compareTo(new ArbitraryNumber(9, 3)); // 1 (10000 > 9000)
|
|
548
|
-
* new ArbitraryNumber(-1, 4).compareTo(new ArbitraryNumber(1, 3)); // -1 (-10000 < 1000)
|
|
549
996
|
*/
|
|
550
997
|
compareTo(other) {
|
|
551
998
|
const thisNegative = this.coefficient < 0;
|
|
@@ -556,8 +1003,8 @@ var _ArbitraryNumber = class _ArbitraryNumber {
|
|
|
556
1003
|
if (this.exponent !== other.exponent) {
|
|
557
1004
|
if (this.coefficient === 0) return otherNegative ? 1 : -1;
|
|
558
1005
|
if (other.coefficient === 0) return thisNegative ? -1 : 1;
|
|
559
|
-
const
|
|
560
|
-
return thisNegative ?
|
|
1006
|
+
const thisExpHigher = this.exponent > other.exponent;
|
|
1007
|
+
return thisNegative ? thisExpHigher ? -1 : 1 : thisExpHigher ? 1 : -1;
|
|
561
1008
|
}
|
|
562
1009
|
if (this.coefficient !== other.coefficient) {
|
|
563
1010
|
return this.coefficient > other.coefficient ? 1 : -1;
|
|
@@ -584,375 +1031,288 @@ var _ArbitraryNumber = class _ArbitraryNumber {
|
|
|
584
1031
|
equals(other) {
|
|
585
1032
|
return this.compareTo(other) === 0;
|
|
586
1033
|
}
|
|
1034
|
+
// ── Clamp / min / max (static, allocate) ──────────────────────────────────
|
|
587
1035
|
/**
|
|
588
|
-
*
|
|
589
|
-
*
|
|
590
|
-
* Numbers with `exponent >= PrecisionCutoff` are already integers at that scale
|
|
591
|
-
* and are returned unchanged.
|
|
592
|
-
*
|
|
593
|
-
* @example
|
|
594
|
-
* new ArbitraryNumber(1.7, 0).floor(); // 1
|
|
595
|
-
* new ArbitraryNumber(-1.7, 0).floor(); // -2
|
|
596
|
-
*/
|
|
597
|
-
floor() {
|
|
598
|
-
if (this.coefficient === 0) {
|
|
599
|
-
return _ArbitraryNumber.Zero;
|
|
600
|
-
}
|
|
601
|
-
if (this.exponent >= _ArbitraryNumber.PrecisionCutoff) {
|
|
602
|
-
return this;
|
|
603
|
-
}
|
|
604
|
-
if (this.exponent < 0) {
|
|
605
|
-
return this.coefficient >= 0 ? _ArbitraryNumber.Zero : new _ArbitraryNumber(-1, 0);
|
|
606
|
-
}
|
|
607
|
-
return new _ArbitraryNumber(Math.floor(this.coefficient * pow10(this.exponent)), 0);
|
|
608
|
-
}
|
|
609
|
-
/**
|
|
610
|
-
* Returns the smallest integer greater than or equal to this number (ceil toward +Infinity).
|
|
611
|
-
*
|
|
612
|
-
* Numbers with `exponent >= PrecisionCutoff` are already integers at that scale
|
|
613
|
-
* and are returned unchanged.
|
|
614
|
-
*
|
|
615
|
-
* @example
|
|
616
|
-
* new ArbitraryNumber(1.2, 0).ceil(); // 2
|
|
617
|
-
* new ArbitraryNumber(-1.7, 0).ceil(); // -1
|
|
618
|
-
*/
|
|
619
|
-
ceil() {
|
|
620
|
-
if (this.coefficient === 0) {
|
|
621
|
-
return _ArbitraryNumber.Zero;
|
|
622
|
-
}
|
|
623
|
-
if (this.exponent >= _ArbitraryNumber.PrecisionCutoff) {
|
|
624
|
-
return this;
|
|
625
|
-
}
|
|
626
|
-
if (this.exponent < 0) {
|
|
627
|
-
return this.coefficient > 0 ? _ArbitraryNumber.One : _ArbitraryNumber.Zero;
|
|
628
|
-
}
|
|
629
|
-
return new _ArbitraryNumber(Math.ceil(this.coefficient * pow10(this.exponent)), 0);
|
|
630
|
-
}
|
|
631
|
-
/**
|
|
632
|
-
* Clamps `value` to the inclusive range `[min, max]`.
|
|
633
|
-
*
|
|
634
|
-
* @example
|
|
635
|
-
* ArbitraryNumber.clamp(new ArbitraryNumber(5, 2), new ArbitraryNumber(1, 3), new ArbitraryNumber(2, 3)); // 1*10^3 (500 clamped to [1000, 2000])
|
|
636
|
-
*
|
|
637
|
-
* @param value - The value to clamp.
|
|
638
|
-
* @param min - Lower bound (inclusive).
|
|
639
|
-
* @param max - Upper bound (inclusive).
|
|
1036
|
+
* Clamps `value` to the inclusive range `[min, max]`. Returns one of the three
|
|
1037
|
+
* inputs (no allocation).
|
|
640
1038
|
*/
|
|
641
1039
|
static clamp(value, min, max) {
|
|
642
1040
|
if (value.lessThan(min)) return min;
|
|
643
1041
|
if (value.greaterThan(max)) return max;
|
|
644
1042
|
return value;
|
|
645
1043
|
}
|
|
646
|
-
/**
|
|
647
|
-
* Returns the smaller of `a` and `b`.
|
|
648
|
-
* @example ArbitraryNumber.min(a, b)
|
|
649
|
-
*/
|
|
1044
|
+
/** Returns the smaller of `a` and `b`. */
|
|
650
1045
|
static min(a, b) {
|
|
651
1046
|
return a.lessThan(b) ? a : b;
|
|
652
1047
|
}
|
|
653
|
-
/**
|
|
654
|
-
* Returns the larger of `a` and `b`.
|
|
655
|
-
* @example ArbitraryNumber.max(a, b)
|
|
656
|
-
*/
|
|
1048
|
+
/** Returns the larger of `a` and `b`. */
|
|
657
1049
|
static max(a, b) {
|
|
658
1050
|
return a.greaterThan(b) ? a : b;
|
|
659
1051
|
}
|
|
660
1052
|
/**
|
|
661
|
-
* Linear interpolation: `a + (b - a) * t
|
|
1053
|
+
* Linear interpolation: `a + (b - a) * t`.
|
|
662
1054
|
*
|
|
663
|
-
*
|
|
664
|
-
*
|
|
665
|
-
*
|
|
666
|
-
* @param t - Interpolation factor as a plain `number`. Values outside [0, 1] are allowed (extrapolation).
|
|
667
|
-
* @example
|
|
668
|
-
* ArbitraryNumber.lerp(an(100), an(200), 0.5); // 150
|
|
1055
|
+
* Returns `a` unchanged when `t === 0`, `b` unchanged when `t === 1`.
|
|
1056
|
+
* All other values of `t` allocate and return a fresh instance.
|
|
669
1057
|
*/
|
|
670
1058
|
static lerp(a, b, t) {
|
|
671
1059
|
if (t === 0) return a;
|
|
672
1060
|
if (t === 1) return b;
|
|
673
|
-
return a.add(b.sub(a).mul(_ArbitraryNumber.from(t)));
|
|
1061
|
+
return a.clone().add(b.clone().sub(a).mul(_ArbitraryNumber.from(t)));
|
|
674
1062
|
}
|
|
675
1063
|
/**
|
|
676
|
-
* Runs `fn` with `
|
|
677
|
-
*
|
|
678
|
-
* Useful when one section of code needs different precision than the rest.
|
|
679
|
-
*
|
|
680
|
-
* @example
|
|
681
|
-
* // Run financial calculation with higher precision
|
|
682
|
-
* const result = ArbitraryNumber.withPrecision(50, () => a.add(b));
|
|
1064
|
+
* Runs `fn` with `defaults.scaleCutoff` temporarily set to `cutoff`, then restores it.
|
|
683
1065
|
*/
|
|
684
1066
|
static withPrecision(cutoff, fn) {
|
|
685
|
-
const prev = _ArbitraryNumber.
|
|
686
|
-
_ArbitraryNumber.
|
|
1067
|
+
const prev = _ArbitraryNumber.defaults.scaleCutoff;
|
|
1068
|
+
_ArbitraryNumber.defaults.scaleCutoff = cutoff;
|
|
1069
|
+
_scaleCutoff = cutoff;
|
|
687
1070
|
try {
|
|
688
1071
|
return fn();
|
|
689
1072
|
} finally {
|
|
690
|
-
_ArbitraryNumber.
|
|
1073
|
+
_ArbitraryNumber.defaults.scaleCutoff = prev;
|
|
1074
|
+
_scaleCutoff = prev;
|
|
691
1075
|
}
|
|
692
1076
|
}
|
|
1077
|
+
// ── Predicates (read-only) ─────────────────────────────────────────────────
|
|
1078
|
+
/** Returns `true` when this number is zero. */
|
|
1079
|
+
isZero() {
|
|
1080
|
+
return this.coefficient === 0;
|
|
1081
|
+
}
|
|
1082
|
+
/** Returns `true` when this number is strictly positive. */
|
|
1083
|
+
isPositive() {
|
|
1084
|
+
return this.coefficient > 0;
|
|
1085
|
+
}
|
|
1086
|
+
/** Returns `true` when this number is strictly negative. */
|
|
1087
|
+
isNegative() {
|
|
1088
|
+
return this.coefficient < 0;
|
|
1089
|
+
}
|
|
693
1090
|
/**
|
|
694
|
-
* Returns `
|
|
695
|
-
*
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
1091
|
+
* Returns `true` when this number has no fractional part.
|
|
1092
|
+
* Numbers with `exponent >= scaleCutoff` are always considered integers.
|
|
1093
|
+
*/
|
|
1094
|
+
isInteger() {
|
|
1095
|
+
if (this.coefficient === 0) return true;
|
|
1096
|
+
if (this.exponent >= _scaleCutoff) return true;
|
|
1097
|
+
if (this.exponent < 0) return false;
|
|
1098
|
+
return Number.isInteger(this.coefficient * pow10(this.exponent));
|
|
1099
|
+
}
|
|
1100
|
+
/**
|
|
1101
|
+
* Returns `1` if positive, `-1` if negative, `0` if zero.
|
|
1102
|
+
*/
|
|
1103
|
+
sign() {
|
|
1104
|
+
return Math.sign(this.coefficient);
|
|
1105
|
+
}
|
|
1106
|
+
// ── Freeze ─────────────────────────────────────────────────────────────────
|
|
1107
|
+
/**
|
|
1108
|
+
* Returns a `FrozenArbitraryNumber` wrapping the same value.
|
|
702
1109
|
*
|
|
703
|
-
*
|
|
704
|
-
*
|
|
1110
|
+
* Mutating methods on the frozen instance throw `ArbitraryNumberMutationError`.
|
|
1111
|
+
* Call `.clone()` on the frozen instance to get a fresh, mutable copy.
|
|
705
1112
|
*/
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
throw new ArbitraryNumberDomainError("Logarithm of zero is undefined", { value: 0 });
|
|
709
|
-
}
|
|
710
|
-
if (this.coefficient < 0) {
|
|
711
|
-
throw new ArbitraryNumberDomainError("Logarithm of a negative number is undefined", { value: this.toNumber() });
|
|
712
|
-
}
|
|
713
|
-
return Math.log10(this.coefficient) + this.exponent;
|
|
1113
|
+
freeze() {
|
|
1114
|
+
return new FrozenArbitraryNumber(this.coefficient, this.exponent);
|
|
714
1115
|
}
|
|
1116
|
+
// ── Serialization ──────────────────────────────────────────────────────────
|
|
715
1117
|
/**
|
|
716
|
-
*
|
|
1118
|
+
* Converts to a plain JavaScript `number`.
|
|
717
1119
|
*
|
|
718
|
-
*
|
|
719
|
-
*
|
|
720
|
-
|
|
1120
|
+
* Returns `Infinity` for exponents beyond float64 range (>=308).
|
|
1121
|
+
* Returns `0` for exponents below float64 range (<=-324).
|
|
1122
|
+
*/
|
|
1123
|
+
toNumber() {
|
|
1124
|
+
return this.coefficient * pow10(this.exponent);
|
|
1125
|
+
}
|
|
1126
|
+
/**
|
|
1127
|
+
* Returns a stable, minimal JSON representation: `{ c: number, e: number }`.
|
|
721
1128
|
*
|
|
722
|
-
*
|
|
723
|
-
* @example
|
|
724
|
-
* new ArbitraryNumber(4, 0).sqrt(); // 2
|
|
725
|
-
* new ArbitraryNumber(1, 4).sqrt(); // 1*10^2 (= 100)
|
|
1129
|
+
* Round-trip guarantee: `ArbitraryNumber.fromJSON(x.toJSON()).equals(x)` is always `true`.
|
|
726
1130
|
*/
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
if (this.coefficient === 0) return _ArbitraryNumber.Zero;
|
|
730
|
-
if (this.exponent % 2 !== 0) {
|
|
731
|
-
return _ArbitraryNumber.createNormalized(Math.sqrt(this.coefficient * 10), (this.exponent - 1) / 2);
|
|
732
|
-
}
|
|
733
|
-
return _ArbitraryNumber.createNormalized(Math.sqrt(this.coefficient), this.exponent / 2);
|
|
1131
|
+
toJSON() {
|
|
1132
|
+
return { c: this.coefficient, e: this.exponent };
|
|
734
1133
|
}
|
|
735
1134
|
/**
|
|
736
|
-
* Returns
|
|
1135
|
+
* Returns a compact string representation: `"<coefficient>|<exponent>"`.
|
|
737
1136
|
*
|
|
738
|
-
*
|
|
739
|
-
* and are returned unchanged.
|
|
1137
|
+
* Shorter than JSON for save-game serialisation. Reconstruct via `ArbitraryNumber.parse`.
|
|
740
1138
|
*
|
|
741
1139
|
* @example
|
|
742
|
-
*
|
|
743
|
-
* new ArbitraryNumber(1.4, 0).round(); // 1
|
|
744
|
-
* new ArbitraryNumber(-1.5, 0).round(); // -1 (half-up toward positive infinity)
|
|
1140
|
+
* an(1500).toRawString() // "1.5|3"
|
|
745
1141
|
*/
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
if (this.exponent >= _ArbitraryNumber.PrecisionCutoff) return this;
|
|
749
|
-
if (this.exponent < 0) {
|
|
750
|
-
if (this.exponent <= -2) return _ArbitraryNumber.Zero;
|
|
751
|
-
const rounded = Math.round(this.coefficient * 0.1);
|
|
752
|
-
return rounded === 0 ? _ArbitraryNumber.Zero : new _ArbitraryNumber(rounded, 0);
|
|
753
|
-
}
|
|
754
|
-
return new _ArbitraryNumber(Math.round(this.coefficient * pow10(this.exponent)), 0);
|
|
1142
|
+
toRawString() {
|
|
1143
|
+
return `${this.coefficient}|${this.exponent}`;
|
|
755
1144
|
}
|
|
756
1145
|
/**
|
|
757
|
-
*
|
|
1146
|
+
* Reconstructs an `ArbitraryNumber` from a `toJSON()` blob.
|
|
758
1147
|
*
|
|
759
|
-
* @
|
|
760
|
-
* new ArbitraryNumber(1.5, 3).sign(); // 1
|
|
761
|
-
* new ArbitraryNumber(-1.5, 3).sign(); // -1
|
|
762
|
-
* ArbitraryNumber.Zero.sign(); // 0
|
|
1148
|
+
* @throws `ArbitraryNumberInputError` when the object shape is invalid or values are non-finite.
|
|
763
1149
|
*/
|
|
764
|
-
|
|
765
|
-
|
|
1150
|
+
static fromJSON(obj) {
|
|
1151
|
+
if (obj === null || typeof obj !== "object" || !("c" in obj) || !("e" in obj) || typeof obj.c !== "number" || typeof obj.e !== "number") {
|
|
1152
|
+
throw new ArbitraryNumberInputError(
|
|
1153
|
+
"ArbitraryNumber.fromJSON: expected { c: number, e: number }",
|
|
1154
|
+
String(obj)
|
|
1155
|
+
);
|
|
1156
|
+
}
|
|
1157
|
+
const { c, e } = obj;
|
|
1158
|
+
if (!isFinite(c)) {
|
|
1159
|
+
throw new ArbitraryNumberInputError("ArbitraryNumber.fromJSON: c must be finite", c);
|
|
1160
|
+
}
|
|
1161
|
+
if (!isFinite(e)) {
|
|
1162
|
+
throw new ArbitraryNumberInputError("ArbitraryNumber.fromJSON: e must be finite", e);
|
|
1163
|
+
}
|
|
1164
|
+
return _ArbitraryNumber._normalizeNew(c, e);
|
|
766
1165
|
}
|
|
767
1166
|
/**
|
|
768
|
-
*
|
|
1167
|
+
* Parses a string into an `ArbitraryNumber`.
|
|
769
1168
|
*
|
|
770
|
-
*
|
|
771
|
-
*
|
|
772
|
-
*
|
|
1169
|
+
* Accepted formats:
|
|
1170
|
+
* - Raw pipe format: `"1.5|3"`, `"-2.5|-6"`
|
|
1171
|
+
* - Scientific notation: `"1.5e+3"`, `"1.5E3"`
|
|
1172
|
+
* - Plain decimal: `"1500"`, `"-0.003"`, `"0"`
|
|
773
1173
|
*
|
|
774
|
-
* @
|
|
775
|
-
* new ArbitraryNumber(1.5, 3).toNumber(); // 1500
|
|
776
|
-
* new ArbitraryNumber(1, 400).toNumber(); // Infinity
|
|
1174
|
+
* @throws `ArbitraryNumberInputError` for invalid or non-finite input.
|
|
777
1175
|
*/
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
1176
|
+
static parse(s) {
|
|
1177
|
+
if (typeof s !== "string" || s.trim() === "") {
|
|
1178
|
+
throw new ArbitraryNumberInputError("ArbitraryNumber.parse: input must be a non-empty string", s);
|
|
1179
|
+
}
|
|
1180
|
+
const trimmed = s.trim();
|
|
1181
|
+
const pipeIdx = trimmed.indexOf("|");
|
|
1182
|
+
if (pipeIdx !== -1) {
|
|
1183
|
+
const cStr = trimmed.slice(0, pipeIdx);
|
|
1184
|
+
const eStr = trimmed.slice(pipeIdx + 1);
|
|
1185
|
+
const c = Number(cStr);
|
|
1186
|
+
const e = Number(eStr);
|
|
1187
|
+
if (!isFinite(c) || !isFinite(e) || cStr === "" || eStr === "") {
|
|
1188
|
+
throw new ArbitraryNumberInputError("ArbitraryNumber.parse: invalid pipe format", s);
|
|
1189
|
+
}
|
|
1190
|
+
return _ArbitraryNumber._normalizeNew(c, e);
|
|
1191
|
+
}
|
|
1192
|
+
const n = Number(trimmed);
|
|
1193
|
+
if (!isFinite(n)) {
|
|
1194
|
+
throw new ArbitraryNumberInputError("ArbitraryNumber.parse: value is not finite", s);
|
|
1195
|
+
}
|
|
1196
|
+
return new _ArbitraryNumber(n, 0);
|
|
792
1197
|
}
|
|
1198
|
+
// ── String coercion ────────────────────────────────────────────────────────
|
|
793
1199
|
/**
|
|
794
|
-
*
|
|
795
|
-
*
|
|
1200
|
+
* Allows implicit coercion via `+an(1500)` (returns `toNumber()`) and
|
|
1201
|
+
* template literals / string concatenation (returns `toString()`).
|
|
796
1202
|
*/
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
1203
|
+
[Symbol.toPrimitive](hint) {
|
|
1204
|
+
return hint === "number" ? this.toNumber() : this.toString();
|
|
1205
|
+
}
|
|
1206
|
+
/** Custom Node.js inspect output so `console.log(an(1500))` renders `"1.50e+3"`. */
|
|
1207
|
+
[/* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom")]() {
|
|
1208
|
+
return this.toString();
|
|
802
1209
|
}
|
|
803
1210
|
/**
|
|
804
1211
|
* Formats this number as a string using the given notation plugin.
|
|
805
1212
|
*
|
|
806
|
-
* Defaults to
|
|
807
|
-
* `decimals` controls
|
|
808
|
-
*
|
|
809
|
-
* @example
|
|
810
|
-
* new ArbitraryNumber(1.5, 3).toString(); // "1.50e+3"
|
|
811
|
-
* new ArbitraryNumber(1.5, 3).toString(unitNotation); // "1.50 K"
|
|
812
|
-
* new ArbitraryNumber(1.5, 3).toString(unitNotation, 4); // "1.5000 K"
|
|
813
|
-
*
|
|
814
|
-
* @param notation - The formatting plugin to use.
|
|
815
|
-
* @param decimals - Number of decimal places to render. Defaults to `2`.
|
|
1213
|
+
* Defaults to `scientificNotation` when no plugin is provided.
|
|
1214
|
+
* `decimals` controls decimal places and defaults to `defaults.notationDecimals`.
|
|
816
1215
|
*/
|
|
817
|
-
toString(notation = scientificNotation, decimals =
|
|
1216
|
+
toString(notation = scientificNotation, decimals = _ArbitraryNumber.defaults.notationDecimals) {
|
|
818
1217
|
return notation.format(this.coefficient, this.exponent, decimals);
|
|
819
1218
|
}
|
|
820
1219
|
};
|
|
821
|
-
/**
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
*
|
|
828
|
-
* Default: 15 (matches float64 coefficient precision of ~15.95 significant digits).
|
|
829
|
-
* Game patterns: diffs 0-8 (exact), prestige 15-25 (loss <0.0001%), idle 20-50 (~0.1% loss).
|
|
830
|
-
*
|
|
831
|
-
* Override globally via assignment, or use {@link withPrecision} for a scoped block.
|
|
832
|
-
*/
|
|
833
|
-
_ArbitraryNumber.PrecisionCutoff = 15;
|
|
834
|
-
/** The additive identity: `0`. */
|
|
1220
|
+
/** Global tunables. Mutating these is a process-level change. */
|
|
1221
|
+
_ArbitraryNumber.defaults = {
|
|
1222
|
+
scaleCutoff: 15,
|
|
1223
|
+
notationDecimals: 2
|
|
1224
|
+
};
|
|
1225
|
+
/** The additive identity: `0`. Frozen — calling mutating methods throws. */
|
|
835
1226
|
_ArbitraryNumber.Zero = new _ArbitraryNumber(0, 0);
|
|
836
|
-
/** The multiplicative identity: `1`. */
|
|
1227
|
+
/** The multiplicative identity: `1`. Frozen — calling mutating methods throws. */
|
|
837
1228
|
_ArbitraryNumber.One = new _ArbitraryNumber(1, 0);
|
|
838
|
-
/** `10`. */
|
|
1229
|
+
/** `10`. Frozen — calling mutating methods throws. */
|
|
839
1230
|
_ArbitraryNumber.Ten = new _ArbitraryNumber(1, 1);
|
|
840
1231
|
var ArbitraryNumber = _ArbitraryNumber;
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
// src/core/AnChain.ts
|
|
848
|
-
var AnChain = class _AnChain {
|
|
849
|
-
constructor(start) {
|
|
850
|
-
this.value = start;
|
|
1232
|
+
var FrozenArbitraryNumber = class extends ArbitraryNumber {
|
|
1233
|
+
/** @internal */
|
|
1234
|
+
constructor(coefficient, exponent) {
|
|
1235
|
+
super(0, 0);
|
|
1236
|
+
this.coefficient = coefficient;
|
|
1237
|
+
this.exponent = exponent;
|
|
851
1238
|
}
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
return new _AnChain(ArbitraryNumber.from(value));
|
|
1239
|
+
_throwMutation(method) {
|
|
1240
|
+
throw new ArbitraryNumberMutationError(
|
|
1241
|
+
`Cannot call mutating method '${method}' on a frozen ArbitraryNumber`
|
|
1242
|
+
);
|
|
857
1243
|
}
|
|
858
|
-
//
|
|
859
|
-
|
|
860
|
-
add(
|
|
861
|
-
this.
|
|
862
|
-
return this;
|
|
1244
|
+
// Every public mutating method on ArbitraryNumber must have an override here.
|
|
1245
|
+
// If you add a new mutating method above, add a matching override below.
|
|
1246
|
+
add(_other) {
|
|
1247
|
+
this._throwMutation("add");
|
|
863
1248
|
}
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
this.value = this.value.sub(other);
|
|
867
|
-
return this;
|
|
1249
|
+
sub(_other) {
|
|
1250
|
+
this._throwMutation("sub");
|
|
868
1251
|
}
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
this.value = this.value.mul(other);
|
|
872
|
-
return this;
|
|
1252
|
+
mul(_other) {
|
|
1253
|
+
this._throwMutation("mul");
|
|
873
1254
|
}
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
this.value = this.value.div(other);
|
|
877
|
-
return this;
|
|
1255
|
+
div(_other) {
|
|
1256
|
+
this._throwMutation("div");
|
|
878
1257
|
}
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
this.value = this.value.pow(exp);
|
|
882
|
-
return this;
|
|
1258
|
+
negate() {
|
|
1259
|
+
this._throwMutation("negate");
|
|
883
1260
|
}
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
mulAdd(mult, add) {
|
|
887
|
-
this.value = this.value.mulAdd(mult, add);
|
|
888
|
-
return this;
|
|
1261
|
+
abs() {
|
|
1262
|
+
this._throwMutation("abs");
|
|
889
1263
|
}
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
this.value = this.value.addMul(add, mult);
|
|
893
|
-
return this;
|
|
1264
|
+
pow(_n) {
|
|
1265
|
+
this._throwMutation("pow");
|
|
894
1266
|
}
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
this.value = this.value.mulSub(mult, sub);
|
|
898
|
-
return this;
|
|
1267
|
+
mulAdd(_m, _a) {
|
|
1268
|
+
this._throwMutation("mulAdd");
|
|
899
1269
|
}
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
this.value = this.value.subMul(sub, mult);
|
|
903
|
-
return this;
|
|
1270
|
+
addMul(_a, _m) {
|
|
1271
|
+
this._throwMutation("addMul");
|
|
904
1272
|
}
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
this.value = this.value.divAdd(div, add);
|
|
908
|
-
return this;
|
|
1273
|
+
mulSub(_m, _s) {
|
|
1274
|
+
this._throwMutation("mulSub");
|
|
909
1275
|
}
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
abs() {
|
|
913
|
-
this.value = this.value.abs();
|
|
914
|
-
return this;
|
|
1276
|
+
subMul(_s, _m) {
|
|
1277
|
+
this._throwMutation("subMul");
|
|
915
1278
|
}
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
this.value = this.value.negate();
|
|
919
|
-
return this;
|
|
1279
|
+
divAdd(_d, _a) {
|
|
1280
|
+
this._throwMutation("divAdd");
|
|
920
1281
|
}
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
this.value = this.value.sqrt();
|
|
924
|
-
return this;
|
|
1282
|
+
mulDiv(_m, _d) {
|
|
1283
|
+
this._throwMutation("mulDiv");
|
|
925
1284
|
}
|
|
926
|
-
/** Rounds down to the nearest integer. */
|
|
927
1285
|
floor() {
|
|
928
|
-
this.
|
|
929
|
-
return this;
|
|
1286
|
+
this._throwMutation("floor");
|
|
930
1287
|
}
|
|
931
|
-
/** Rounds up to the nearest integer. */
|
|
932
1288
|
ceil() {
|
|
933
|
-
this.
|
|
934
|
-
return this;
|
|
1289
|
+
this._throwMutation("ceil");
|
|
935
1290
|
}
|
|
936
|
-
/** Rounds to the nearest integer. */
|
|
937
1291
|
round() {
|
|
938
|
-
this.
|
|
939
|
-
|
|
1292
|
+
this._throwMutation("round");
|
|
1293
|
+
}
|
|
1294
|
+
trunc() {
|
|
1295
|
+
this._throwMutation("trunc");
|
|
940
1296
|
}
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
1297
|
+
sqrt() {
|
|
1298
|
+
this._throwMutation("sqrt");
|
|
1299
|
+
}
|
|
1300
|
+
cbrt() {
|
|
1301
|
+
this._throwMutation("cbrt");
|
|
945
1302
|
}
|
|
946
1303
|
};
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
1304
|
+
ArbitraryNumber.Zero = new FrozenArbitraryNumber(0, 0);
|
|
1305
|
+
ArbitraryNumber.One = new FrozenArbitraryNumber(1, 0);
|
|
1306
|
+
ArbitraryNumber.Ten = new FrozenArbitraryNumber(1, 1);
|
|
1307
|
+
|
|
1308
|
+
// src/core/an.ts
|
|
1309
|
+
var createAn = ((coefficient, exponent = 0) => new ArbitraryNumber(coefficient, exponent));
|
|
1310
|
+
createAn.from = (value) => ArbitraryNumber.from(value);
|
|
1311
|
+
var an = createAn;
|
|
950
1312
|
|
|
951
1313
|
// src/core/AnFormula.ts
|
|
952
1314
|
var AnFormula = class _AnFormula {
|
|
953
|
-
/**
|
|
954
|
-
* Prefer the {@link formula} factory function over calling this directly.
|
|
955
|
-
*/
|
|
1315
|
+
/** Prefer the {@link formula} factory function over calling this directly. */
|
|
956
1316
|
constructor(name, steps = []) {
|
|
957
1317
|
this._name = name;
|
|
958
1318
|
this.steps = steps;
|
|
@@ -963,18 +1323,10 @@ var AnFormula = class _AnFormula {
|
|
|
963
1323
|
}
|
|
964
1324
|
/**
|
|
965
1325
|
* Returns a copy of this formula with a new name, leaving the original unchanged.
|
|
966
|
-
*
|
|
967
|
-
* @param name - The new name.
|
|
968
|
-
* @example
|
|
969
|
-
* const base = formula().mul(an(2));
|
|
970
|
-
* const named = base.named("Double");
|
|
971
|
-
* named.name // "Double"
|
|
972
|
-
* base.name // undefined
|
|
973
1326
|
*/
|
|
974
1327
|
named(name) {
|
|
975
1328
|
return new _AnFormula(name, this.steps);
|
|
976
1329
|
}
|
|
977
|
-
// ── Private helper ────────────────────────────────────────────────────────
|
|
978
1330
|
step(fn) {
|
|
979
1331
|
return new _AnFormula(this._name, [...this.steps, fn]);
|
|
980
1332
|
}
|
|
@@ -999,7 +1351,7 @@ var AnFormula = class _AnFormula {
|
|
|
999
1351
|
pow(exp) {
|
|
1000
1352
|
return this.step((v) => v.pow(exp));
|
|
1001
1353
|
}
|
|
1002
|
-
// ── Fused
|
|
1354
|
+
// ── Fused ─────────────────────────────────────────────────────────────────
|
|
1003
1355
|
/** Appends `(value * mult) + add` to the pipeline. */
|
|
1004
1356
|
mulAdd(mult, add) {
|
|
1005
1357
|
return this.step((v) => v.mulAdd(mult, add));
|
|
@@ -1025,7 +1377,7 @@ var AnFormula = class _AnFormula {
|
|
|
1025
1377
|
abs() {
|
|
1026
1378
|
return this.step((v) => v.abs());
|
|
1027
1379
|
}
|
|
1028
|
-
/** Appends `
|
|
1380
|
+
/** Appends `negate()` to the pipeline. */
|
|
1029
1381
|
neg() {
|
|
1030
1382
|
return this.step((v) => v.negate());
|
|
1031
1383
|
}
|
|
@@ -1050,34 +1402,39 @@ var AnFormula = class _AnFormula {
|
|
|
1050
1402
|
* Returns a new formula that first applies `this`, then applies `next`.
|
|
1051
1403
|
*
|
|
1052
1404
|
* Neither operand is mutated.
|
|
1053
|
-
*
|
|
1054
|
-
* @param next - The formula to apply after `this`.
|
|
1055
|
-
* @example
|
|
1056
|
-
* const full = armorReduction.then(critBonus);
|
|
1057
|
-
* const result = full.apply(baseDamage);
|
|
1058
1405
|
*/
|
|
1059
1406
|
then(next) {
|
|
1060
1407
|
return new _AnFormula(this._name, [...this.steps, ...next.steps]);
|
|
1061
1408
|
}
|
|
1062
1409
|
// ── Terminal ──────────────────────────────────────────────────────────────
|
|
1063
1410
|
/**
|
|
1064
|
-
*
|
|
1411
|
+
* Clones `value` once, runs the pipeline against the clone, and returns it.
|
|
1065
1412
|
*
|
|
1066
|
-
* The
|
|
1413
|
+
* The original `value` is never mutated.
|
|
1067
1414
|
*
|
|
1068
|
-
* @param value - The starting value. Plain `number` is coerced via `ArbitraryNumber.from`.
|
|
1069
|
-
* @throws `"ArbitraryNumber.from: value must be finite"` when a plain `number` is non-finite.
|
|
1070
1415
|
* @example
|
|
1071
|
-
* const damage = damageFormula.apply(
|
|
1072
|
-
* const scaled = damageFormula.apply(boostedBase);
|
|
1416
|
+
* const damage = damageFormula.apply(playerAtk); // playerAtk unchanged
|
|
1073
1417
|
*/
|
|
1074
1418
|
apply(value) {
|
|
1075
|
-
|
|
1419
|
+
const result = value instanceof ArbitraryNumber ? value.clone() : ArbitraryNumber.from(value);
|
|
1076
1420
|
for (const step of this.steps) {
|
|
1077
|
-
|
|
1421
|
+
step(result);
|
|
1078
1422
|
}
|
|
1079
1423
|
return result;
|
|
1080
1424
|
}
|
|
1425
|
+
/**
|
|
1426
|
+
* Runs the pipeline directly against `value`, mutating it in-place.
|
|
1427
|
+
*
|
|
1428
|
+
* Use on hot paths where you don't need to preserve the original value.
|
|
1429
|
+
*
|
|
1430
|
+
* @example
|
|
1431
|
+
* damageFormula.applyInPlace(enemyAtk); // enemyAtk is mutated
|
|
1432
|
+
*/
|
|
1433
|
+
applyInPlace(value) {
|
|
1434
|
+
for (const step of this.steps) {
|
|
1435
|
+
step(value);
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1081
1438
|
};
|
|
1082
1439
|
function formula(name) {
|
|
1083
1440
|
return new AnFormula(name, []);
|
|
@@ -1105,7 +1462,7 @@ var SuffixNotationBase = class {
|
|
|
1105
1462
|
*/
|
|
1106
1463
|
format(coefficient, exponent, decimals) {
|
|
1107
1464
|
if (exponent < 0) {
|
|
1108
|
-
return (coefficient
|
|
1465
|
+
return (coefficient / pow10(-exponent)).toFixed(decimals);
|
|
1109
1466
|
}
|
|
1110
1467
|
const tier = Math.floor(exponent / 3);
|
|
1111
1468
|
const remainder = exponent - tier * 3;
|
|
@@ -1120,6 +1477,7 @@ var SuffixNotationBase = class {
|
|
|
1120
1477
|
function alphabetSuffix(tier, alphabet = "abcdefghijklmnopqrstuvwxyz") {
|
|
1121
1478
|
if (alphabet.length === 0) throw new ArbitraryNumberInputError("alphabet must not be empty", alphabet);
|
|
1122
1479
|
if (tier <= 0) return "";
|
|
1480
|
+
if (alphabet.length === 1) return alphabet.repeat(tier);
|
|
1123
1481
|
const index = tier - 1;
|
|
1124
1482
|
let length = 1;
|
|
1125
1483
|
let capacity = alphabet.length;
|
|
@@ -1278,21 +1636,40 @@ var UnitNotation = class extends SuffixNotationBase {
|
|
|
1278
1636
|
/**
|
|
1279
1637
|
* @param options - Tier-indexed unit array, optional suffix fallback plugin, and separator.
|
|
1280
1638
|
* `separator` defaults to `" "` (a space between number and unit symbol).
|
|
1639
|
+
* `offsetFallback` defaults to `true` — the fallback tier is offset so its suffixes
|
|
1640
|
+
* are visually distinct from any low-tier suffixes the same fallback would produce.
|
|
1281
1641
|
*/
|
|
1282
1642
|
constructor(options) {
|
|
1283
1643
|
super({ separator: " ", ...options });
|
|
1284
1644
|
this.units = options.units;
|
|
1285
1645
|
this.fallback = options.fallback;
|
|
1646
|
+
this.offsetFallback = options.offsetFallback !== false;
|
|
1647
|
+
let last = 0;
|
|
1648
|
+
for (let i = options.units.length - 1; i >= 0; i--) {
|
|
1649
|
+
if (options.units[i] !== void 0) {
|
|
1650
|
+
last = i;
|
|
1651
|
+
break;
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
this.lastDefinedTier = last;
|
|
1286
1655
|
}
|
|
1287
1656
|
/**
|
|
1288
1657
|
* Returns the suffix for the given tier: the own unit symbol if defined,
|
|
1289
|
-
* otherwise the fallback's suffix
|
|
1658
|
+
* otherwise the fallback's suffix (offset by the last defined tier when
|
|
1659
|
+
* `offsetFallback` is `true`), otherwise `""`.
|
|
1660
|
+
*
|
|
1661
|
+
* The offset ensures fallback suffixes start at tier 1 of the fallback's sequence,
|
|
1662
|
+
* avoiding visual ambiguity with low-tier suffixes from the same fallback plugin.
|
|
1290
1663
|
*
|
|
1291
1664
|
* @param tier - The exponent tier (`Math.floor(exponent / 3)`).
|
|
1292
1665
|
* @returns The suffix string, or `""` if neither own units nor fallback cover this tier.
|
|
1293
1666
|
*/
|
|
1294
1667
|
getSuffix(tier) {
|
|
1295
|
-
|
|
1668
|
+
const ownSymbol = this.units[tier]?.symbol;
|
|
1669
|
+
if (ownSymbol !== void 0) return ownSymbol;
|
|
1670
|
+
if (this.fallback === void 0) return "";
|
|
1671
|
+
const fallbackTier = this.offsetFallback ? tier - this.lastDefinedTier : tier;
|
|
1672
|
+
return this.fallback.getSuffix(fallbackTier) ?? "";
|
|
1296
1673
|
}
|
|
1297
1674
|
};
|
|
1298
1675
|
var unitNotation = new UnitNotation({
|
|
@@ -1312,19 +1689,13 @@ var ArbitraryNumberGuard = class _ArbitraryNumberGuard {
|
|
|
1312
1689
|
return obj instanceof ArbitraryNumber;
|
|
1313
1690
|
}
|
|
1314
1691
|
/**
|
|
1315
|
-
* Returns `true` if `obj` has the shape
|
|
1316
|
-
* (i.e. has numeric `coefficient` and `exponent` properties).
|
|
1317
|
-
*
|
|
1318
|
-
* Note: both `ArbitraryNumber` instances and plain objects with the right
|
|
1319
|
-
* shape will pass this check. Use {@link isArbitraryNumber} when you need
|
|
1320
|
-
* to distinguish between the two.
|
|
1692
|
+
* Returns `true` if `obj` has the shape `{ coefficient: number; exponent: number }`.
|
|
1321
1693
|
*
|
|
1322
|
-
*
|
|
1323
|
-
*
|
|
1324
|
-
* `typeof exponent === "number"`.
|
|
1694
|
+
* Both `ArbitraryNumber` instances and plain objects with the right shape pass this
|
|
1695
|
+
* check. Use {@link isArbitraryNumber} to distinguish the two.
|
|
1325
1696
|
*/
|
|
1326
1697
|
static isNormalizedNumber(obj) {
|
|
1327
|
-
return obj != null && typeof obj
|
|
1698
|
+
return obj != null && typeof obj.coefficient === "number" && typeof obj.exponent === "number";
|
|
1328
1699
|
}
|
|
1329
1700
|
/**
|
|
1330
1701
|
* Returns `true` if `obj` is an {@link ArbitraryNumber} with a value of zero.
|
|
@@ -1337,103 +1708,14 @@ var ArbitraryNumberGuard = class _ArbitraryNumberGuard {
|
|
|
1337
1708
|
}
|
|
1338
1709
|
};
|
|
1339
1710
|
|
|
1340
|
-
// src/utility/ArbitraryNumberOps.ts
|
|
1341
|
-
var ArbitraryNumberOps = class _ArbitraryNumberOps {
|
|
1342
|
-
/**
|
|
1343
|
-
* Converts `value` to an `ArbitraryNumber`, returning it unchanged if it already is one.
|
|
1344
|
-
*
|
|
1345
|
-
* @param value - A plain `number` or an existing `ArbitraryNumber`.
|
|
1346
|
-
* @returns The corresponding `ArbitraryNumber`.
|
|
1347
|
-
*/
|
|
1348
|
-
static from(value) {
|
|
1349
|
-
return value instanceof ArbitraryNumber ? value : ArbitraryNumber.from(value);
|
|
1350
|
-
}
|
|
1351
|
-
/**
|
|
1352
|
-
* Returns `left + right`, coercing both operands as needed.
|
|
1353
|
-
*
|
|
1354
|
-
* @param left - The augend.
|
|
1355
|
-
* @param right - The addend.
|
|
1356
|
-
* @example
|
|
1357
|
-
* ops.add(1500, 2500) // ArbitraryNumber (4000)
|
|
1358
|
-
*/
|
|
1359
|
-
static add(left, right) {
|
|
1360
|
-
return _ArbitraryNumberOps.from(left).add(_ArbitraryNumberOps.from(right));
|
|
1361
|
-
}
|
|
1362
|
-
/**
|
|
1363
|
-
* Returns `left - right`, coercing both operands as needed.
|
|
1364
|
-
*
|
|
1365
|
-
* @param left - The minuend.
|
|
1366
|
-
* @param right - The subtrahend.
|
|
1367
|
-
* @example
|
|
1368
|
-
* ops.sub(5000, 1500) // ArbitraryNumber (3500)
|
|
1369
|
-
*/
|
|
1370
|
-
static sub(left, right) {
|
|
1371
|
-
return _ArbitraryNumberOps.from(left).sub(_ArbitraryNumberOps.from(right));
|
|
1372
|
-
}
|
|
1373
|
-
/**
|
|
1374
|
-
* Returns `left * right`, coercing both operands as needed.
|
|
1375
|
-
*
|
|
1376
|
-
* @param left - The multiplicand.
|
|
1377
|
-
* @param right - The multiplier.
|
|
1378
|
-
* @example
|
|
1379
|
-
* ops.mul(an(1, 3), 5) // ArbitraryNumber (5000)
|
|
1380
|
-
*/
|
|
1381
|
-
static mul(left, right) {
|
|
1382
|
-
return _ArbitraryNumberOps.from(left).mul(_ArbitraryNumberOps.from(right));
|
|
1383
|
-
}
|
|
1384
|
-
/**
|
|
1385
|
-
* Returns `left / right`, coercing both operands as needed.
|
|
1386
|
-
*
|
|
1387
|
-
* @param left - The dividend.
|
|
1388
|
-
* @param right - The divisor.
|
|
1389
|
-
* @throws `"Division by zero"` when `right` is zero.
|
|
1390
|
-
* @example
|
|
1391
|
-
* ops.div(an(1, 6), 1000) // ArbitraryNumber (1000)
|
|
1392
|
-
*/
|
|
1393
|
-
static div(left, right) {
|
|
1394
|
-
return _ArbitraryNumberOps.from(left).div(_ArbitraryNumberOps.from(right));
|
|
1395
|
-
}
|
|
1396
|
-
/**
|
|
1397
|
-
* Compares `left` to `right`.
|
|
1398
|
-
*
|
|
1399
|
-
* @param left - The left operand.
|
|
1400
|
-
* @param right - The right operand.
|
|
1401
|
-
* @returns `1` if `left > right`, `-1` if `left < right`, `0` if equal.
|
|
1402
|
-
* @example
|
|
1403
|
-
* ops.compare(5000, 1500) // 1
|
|
1404
|
-
*/
|
|
1405
|
-
static compare(left, right) {
|
|
1406
|
-
return _ArbitraryNumberOps.from(left).compareTo(_ArbitraryNumberOps.from(right));
|
|
1407
|
-
}
|
|
1408
|
-
/**
|
|
1409
|
-
* Clamps `value` to the inclusive range `[min, max]`, coercing all inputs as needed.
|
|
1410
|
-
*
|
|
1411
|
-
* @param value - The value to clamp.
|
|
1412
|
-
* @param min - The lower bound (inclusive).
|
|
1413
|
-
* @param max - The upper bound (inclusive).
|
|
1414
|
-
* @example
|
|
1415
|
-
* ops.clamp(500, 1000, 2000) // ArbitraryNumber (1000) - below min, returns min
|
|
1416
|
-
*/
|
|
1417
|
-
static clamp(value, min, max) {
|
|
1418
|
-
return ArbitraryNumber.clamp(
|
|
1419
|
-
_ArbitraryNumberOps.from(value),
|
|
1420
|
-
_ArbitraryNumberOps.from(min),
|
|
1421
|
-
_ArbitraryNumberOps.from(max)
|
|
1422
|
-
);
|
|
1423
|
-
}
|
|
1424
|
-
};
|
|
1425
|
-
|
|
1426
1711
|
// src/utility/ArbitraryNumberHelpers.ts
|
|
1427
1712
|
var ArbitraryNumberHelpers = class _ArbitraryNumberHelpers {
|
|
1428
1713
|
static coerce(value) {
|
|
1429
|
-
return
|
|
1714
|
+
return value instanceof ArbitraryNumber ? value : ArbitraryNumber.from(value);
|
|
1430
1715
|
}
|
|
1431
1716
|
/**
|
|
1432
1717
|
* Returns `true` when `value >= threshold`.
|
|
1433
1718
|
*
|
|
1434
|
-
* @param value - The value to test.
|
|
1435
|
-
* @param threshold - The minimum required value.
|
|
1436
|
-
* @returns `true` when `value >= threshold`.
|
|
1437
1719
|
* @example
|
|
1438
1720
|
* ArbitraryNumberHelpers.meetsOrExceeds(gold, upgradeCost)
|
|
1439
1721
|
*/
|
|
@@ -1445,9 +1727,6 @@ var ArbitraryNumberHelpers = class _ArbitraryNumberHelpers {
|
|
|
1445
1727
|
*
|
|
1446
1728
|
* Equivalent to `floor(total / step)`.
|
|
1447
1729
|
*
|
|
1448
|
-
* @param total - The total available amount.
|
|
1449
|
-
* @param step - The cost or size of one unit. Must be greater than zero.
|
|
1450
|
-
* @returns The number of whole units that fit, as an `ArbitraryNumber`.
|
|
1451
1730
|
* @throws `"step must be greater than zero"` when `step <= 0`.
|
|
1452
1731
|
* @example
|
|
1453
1732
|
* const canBuy = ArbitraryNumberHelpers.wholeMultipleCount(gold, upgradeCost);
|
|
@@ -1461,15 +1740,11 @@ var ArbitraryNumberHelpers = class _ArbitraryNumberHelpers {
|
|
|
1461
1740
|
if (numTotal.lessThanOrEqual(ArbitraryNumber.Zero)) {
|
|
1462
1741
|
return ArbitraryNumber.Zero;
|
|
1463
1742
|
}
|
|
1464
|
-
return numTotal.div(numStep).floor();
|
|
1743
|
+
return numTotal.clone().div(numStep).floor();
|
|
1465
1744
|
}
|
|
1466
1745
|
/**
|
|
1467
1746
|
* Returns `value - delta`, clamped to a minimum of `floor` (default `0`).
|
|
1468
1747
|
*
|
|
1469
|
-
* @param value - The starting value.
|
|
1470
|
-
* @param delta - The amount to subtract.
|
|
1471
|
-
* @param floor - The minimum result. Defaults to `ArbitraryNumber.Zero`.
|
|
1472
|
-
* @returns `max(value - delta, floor)`.
|
|
1473
1748
|
* @example
|
|
1474
1749
|
* health = ArbitraryNumberHelpers.subtractWithFloor(health, damage);
|
|
1475
1750
|
*/
|
|
@@ -1477,12 +1752,12 @@ var ArbitraryNumberHelpers = class _ArbitraryNumberHelpers {
|
|
|
1477
1752
|
const numValue = _ArbitraryNumberHelpers.coerce(value);
|
|
1478
1753
|
const numDelta = _ArbitraryNumberHelpers.coerce(delta);
|
|
1479
1754
|
const numFloor = _ArbitraryNumberHelpers.coerce(floor);
|
|
1480
|
-
|
|
1755
|
+
const result = numValue.clone().sub(numDelta);
|
|
1756
|
+
return result.lessThan(numFloor) ? numFloor : result;
|
|
1481
1757
|
}
|
|
1482
1758
|
};
|
|
1483
1759
|
|
|
1484
1760
|
exports.AlphabetNotation = AlphabetNotation;
|
|
1485
|
-
exports.AnChain = AnChain;
|
|
1486
1761
|
exports.AnFormula = AnFormula;
|
|
1487
1762
|
exports.ArbitraryNumber = ArbitraryNumber;
|
|
1488
1763
|
exports.ArbitraryNumberDomainError = ArbitraryNumberDomainError;
|
|
@@ -1490,20 +1765,19 @@ exports.ArbitraryNumberError = ArbitraryNumberError;
|
|
|
1490
1765
|
exports.ArbitraryNumberGuard = ArbitraryNumberGuard;
|
|
1491
1766
|
exports.ArbitraryNumberHelpers = ArbitraryNumberHelpers;
|
|
1492
1767
|
exports.ArbitraryNumberInputError = ArbitraryNumberInputError;
|
|
1493
|
-
exports.
|
|
1768
|
+
exports.ArbitraryNumberMutationError = ArbitraryNumberMutationError;
|
|
1494
1769
|
exports.CLASSIC_UNITS = CLASSIC_UNITS;
|
|
1495
1770
|
exports.COMPACT_UNITS = COMPACT_UNITS;
|
|
1771
|
+
exports.FrozenArbitraryNumber = FrozenArbitraryNumber;
|
|
1496
1772
|
exports.ScientificNotation = ScientificNotation;
|
|
1497
1773
|
exports.SuffixNotationBase = SuffixNotationBase;
|
|
1498
1774
|
exports.UnitNotation = UnitNotation;
|
|
1499
1775
|
exports.alphabetSuffix = alphabetSuffix;
|
|
1500
1776
|
exports.an = an;
|
|
1501
|
-
exports.chain = chain;
|
|
1502
1777
|
exports.formula = formula;
|
|
1503
1778
|
exports.guard = ArbitraryNumberGuard;
|
|
1504
1779
|
exports.helpers = ArbitraryNumberHelpers;
|
|
1505
1780
|
exports.letterNotation = letterNotation;
|
|
1506
|
-
exports.ops = ArbitraryNumberOps;
|
|
1507
1781
|
exports.scientificNotation = scientificNotation;
|
|
1508
1782
|
exports.unitNotation = unitNotation;
|
|
1509
1783
|
//# sourceMappingURL=index.cjs.map
|