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