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