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/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 < 16 ? POW10[n] : Math.pow(10, 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
- * @param coefficient - The significand. Must be a finite number; will be normalised.
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
- * Uses Object.create() to bypass the constructor (zero normalisation cost).
138
- * Only valid when |coefficient| is already in [1, 10) and exponent is correct.
139
- * Do NOT use for unnormalised inputs - call new ArbitraryNumber(c, e) instead.
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 createNormalized(coefficient, exponent) {
142
- if (typeof process !== "undefined" && process.env["NODE_ENV"] !== "production" && coefficient === 0 && exponent !== 0) {
143
- console.warn(`ArbitraryNumber.createNormalized: zero coefficient with non-zero exponent (${exponent}). Use ArbitraryNumber.Zero instead.`);
144
- }
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
- static normalizeFrom(c, e) {
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.createNormalized(c / scale, e + shift);
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
- * Returns `this + other`.
256
+ * Adds `other` to this number in-place.
160
257
  *
161
- * When the exponent difference exceeds {@link PrecisionCutoff}, the smaller
162
- * operand has no effect and the larger is returned as-is.
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
- * new ArbitraryNumber(1.5, 3).add(new ArbitraryNumber(2.5, 3)); // 4*10^3
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
- const cutoff = _ArbitraryNumber.PrecisionCutoff;
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) return other;
174
- let c;
175
- let e;
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 + other.coefficient / pow10Int(diff);
285
+ c = this.coefficient + oc / pow10(diff);
178
286
  e = this.exponent;
179
287
  } else {
180
- c = other.coefficient + this.coefficient / pow10Int(-diff);
181
- e = other.exponent;
288
+ c = oc + this.coefficient / pow10(-diff);
289
+ e = oe;
182
290
  }
183
- return _ArbitraryNumber.normalizeFrom(c, e);
291
+ return this._normalizeInto(c, e);
184
292
  }
185
293
  /**
186
- * Returns `this - other`.
294
+ * Subtracts `other` from this number in-place.
187
295
  *
188
- * @example
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 negC = -other.coefficient;
194
- if (this.coefficient === 0) return _ArbitraryNumber.createNormalized(negC, other.exponent);
195
- const cutoff = _ArbitraryNumber.PrecisionCutoff;
196
- const diff = this.exponent - other.exponent;
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) return _ArbitraryNumber.createNormalized(negC, other.exponent);
199
- let c;
200
- let e;
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 + negC / pow10Int(diff);
318
+ c = this.coefficient + negOc / pow10(diff);
203
319
  e = this.exponent;
204
320
  } else {
205
- c = negC + this.coefficient / pow10Int(-diff);
206
- e = other.exponent;
321
+ c = negOc + this.coefficient / pow10(-diff);
322
+ e = oe;
207
323
  }
208
- return _ArbitraryNumber.normalizeFrom(c, e);
324
+ return this._normalizeInto(c, e);
209
325
  }
210
326
  /**
211
- * Returns `this * other`.
327
+ * Multiplies this number by `other` in-place.
212
328
  *
213
- * @example
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 || other.coefficient === 0) return _ArbitraryNumber.Zero;
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) return _ArbitraryNumber.createNormalized(c / 10, e + 1);
222
- return _ArbitraryNumber.createNormalized(c, e);
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
- * Returns `this / other`.
351
+ * Divides this number by `other` in-place.
226
352
  *
227
- * @example
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) throw new ArbitraryNumberDomainError("Division by zero", { dividend: this.toNumber(), divisor: 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) return _ArbitraryNumber.Zero;
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) return _ArbitraryNumber.createNormalized(c * 10, e - 1);
239
- return _ArbitraryNumber.createNormalized(c, e);
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
- * Returns the arithmetic negation of this number (`-this`).
380
+ * Negates this number in-place.
243
381
  *
244
- * @example
245
- * new ArbitraryNumber(1.5, 3).negate(); // -1.5*10^3
382
+ * **Mutates `this`. Returns `this`.**
246
383
  */
247
384
  negate() {
248
- if (this.coefficient === 0) return _ArbitraryNumber.Zero;
249
- return _ArbitraryNumber.createNormalized(-this.coefficient, this.exponent);
385
+ this.coefficient = -this.coefficient;
386
+ return this;
250
387
  }
251
388
  /**
252
- * Returns the absolute value of this number (`|this|`).
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
- * @example
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 >= 0) return this;
261
- return _ArbitraryNumber.createNormalized(-this.coefficient, this.exponent);
394
+ if (this.coefficient < 0) this.coefficient = -this.coefficient;
395
+ return this;
262
396
  }
263
397
  /**
264
- * Returns `this^n`.
398
+ * Raises this number to the power `n` in-place.
265
399
  *
266
400
  * Supports integer, fractional, and negative exponents.
267
- * `x^0` always returns {@link One}, including `0^0` (by convention).
401
+ * `x^0` always sets `this` to `1`, including `0^0` (by convention).
268
402
  *
269
- * @example
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
- return _ArbitraryNumber.One;
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 _ArbitraryNumber.Zero;
417
+ return this;
285
418
  }
286
419
  if (this.coefficient < 0 && !Number.isInteger(n)) {
287
- throw new ArbitraryNumberDomainError("ArbitraryNumber.pow: fractional exponent of a negative base is not supported", { base: this.toNumber(), exponent: n });
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
- return new _ArbitraryNumber(
293
- Math.pow(this.coefficient, n) * pow10(fracExp),
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
- * Common pattern - prestige loop: `value = value.mulAdd(prestigeMultiplier, prestigeBoost)`
434
+ * Faster than `.mul(multiplier).add(addend)` one normalisation pass total, no
435
+ * intermediate allocation.
304
436
  *
305
- * @example
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) return addend;
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 (addend.coefficient === 0) return _ArbitraryNumber.createNormalized(cp, ep);
319
- const cutoff = _ArbitraryNumber.PrecisionCutoff;
320
- const diff = ep - addend.exponent;
321
- if (diff > cutoff) return _ArbitraryNumber.createNormalized(cp, ep);
322
- if (diff < -cutoff) return addend;
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 + addend.coefficient / pow10Int(diff);
473
+ c = cp + ac / pow10(diff);
326
474
  e = ep;
327
475
  } else {
328
- c = addend.coefficient + cp / pow10Int(-diff);
329
- e = addend.exponent;
476
+ c = ac + cp / pow10(-diff);
477
+ e = ae;
330
478
  }
331
- return _ArbitraryNumber.normalizeFrom(c, e);
479
+ return this._normalizeInto(c, e);
332
480
  }
333
481
  /**
334
- * Fused add-multiply: `(this + addend) * multiplier`.
335
- *
336
- * Faster than `.add(addend).mul(multiplier)` because it avoids allocating an
337
- * intermediate ArbitraryNumber for the sum. One normalisation pass total.
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
- * Common pattern - upgrade calculation: `newValue = baseValue.addMul(bonus, multiplier)`
340
- *
341
- * @example
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
- addMul(addend, multiplier) {
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
- cs = addend.coefficient;
351
- es = addend.exponent;
352
- } else if (addend.coefficient === 0) {
353
- cs = this.coefficient;
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
- const cutoff = _ArbitraryNumber.PrecisionCutoff;
357
- const diff = this.exponent - addend.exponent;
358
- if (diff > cutoff) {
359
- cs = this.coefficient;
360
- es = this.exponent;
361
- } else if (diff < -cutoff) {
362
- cs = addend.coefficient;
363
- es = addend.exponent;
364
- } else {
365
- if (diff >= 0) {
366
- cs = this.coefficient + addend.coefficient / pow10Int(diff);
367
- es = this.exponent;
368
- } else {
369
- cs = addend.coefficient + this.coefficient / pow10Int(-diff);
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
- let cp = cs * multiplier.coefficient;
382
- const ep = es + multiplier.exponent;
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
- return _ArbitraryNumber.createNormalized(cp, ep + 1);
572
+ this.coefficient = cp;
573
+ this.exponent = ep + 1;
574
+ return this;
387
575
  }
388
- return _ArbitraryNumber.createNormalized(cp, ep);
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
- * Common pattern - resource drain: `income.mulSub(rate, upkeepCost)`
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
- if (subtrahend.coefficient === 0) return _ArbitraryNumber.Zero;
400
- return _ArbitraryNumber.createNormalized(-subtrahend.coefficient, subtrahend.exponent);
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 (subtrahend.coefficient === 0) return _ArbitraryNumber.createNormalized(cp, ep);
410
- const cutoff = _ArbitraryNumber.PrecisionCutoff;
411
- const diff = ep - subtrahend.exponent;
412
- if (diff > cutoff) return _ArbitraryNumber.createNormalized(cp, ep);
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
- return _ArbitraryNumber.createNormalized(-subtrahend.coefficient, subtrahend.exponent);
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 - subtrahend.coefficient / pow10Int(diff);
619
+ c = cp - sc / pow10(diff);
419
620
  e = ep;
420
621
  } else {
421
- c = -subtrahend.coefficient + cp / pow10Int(-diff);
422
- e = subtrahend.exponent;
622
+ c = -sc + cp / pow10(-diff);
623
+ e = se;
423
624
  }
424
- return _ArbitraryNumber.normalizeFrom(c, e);
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
- * Avoids one intermediate allocation vs `.sub(subtrahend).mul(multiplier)`.
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) return _ArbitraryNumber.Zero;
435
- let cs, es;
436
- if (this.coefficient === 0 && subtrahend.coefficient === 0) return _ArbitraryNumber.Zero;
437
- if (this.coefficient === 0) {
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
- let cp = cs * multiplier.coefficient;
470
- const ep = es + multiplier.exponent;
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
- return _ArbitraryNumber.createNormalized(cp, ep + 1);
649
+ this.coefficient = cp;
650
+ this.exponent = ep + 1;
651
+ return this;
475
652
  }
476
- return _ArbitraryNumber.createNormalized(cp, ep);
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
- * Common pattern - efficiency bonus: `damage.divAdd(armor, flat)`
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) throw new ArbitraryNumberDomainError("Division by zero", { dividend: this.toNumber(), divisor: 0 });
489
- if (this.coefficient === 0) return addend;
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 (addend.coefficient === 0) return _ArbitraryNumber.createNormalized(cd, ed);
498
- const cutoff = _ArbitraryNumber.PrecisionCutoff;
499
- const diff = ed - addend.exponent;
500
- if (diff > cutoff) return _ArbitraryNumber.createNormalized(cd, ed);
501
- if (diff < -cutoff) return addend;
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 + addend.coefficient / pow10Int(diff);
701
+ c = cd + ac / pow10(diff);
505
702
  e = ed;
506
703
  } else {
507
- c = addend.coefficient + cd / pow10Int(-diff);
508
- e = addend.exponent;
704
+ c = ac + cd / pow10(-diff);
705
+ e = ae;
509
706
  }
510
- return _ArbitraryNumber.normalizeFrom(c, e);
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
- * @example
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) throw new ArbitraryNumberDomainError("Division by zero", { dividend: this.toNumber(), divisor: 0 });
528
- if (this.coefficient === 0 || multiplier.coefficient === 0) return _ArbitraryNumber.Zero;
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) return _ArbitraryNumber.createNormalized(c / 10, e + 1);
533
- if (absC < 1) return _ArbitraryNumber.createNormalized(c * 10, e - 1);
534
- return _ArbitraryNumber.createNormalized(c, e);
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 ArbitraryNumbers in a single normalisation pass.
743
+ * Efficiently sums an array of `ArbitraryNumber`s in a single pass.
538
744
  *
539
- * **Why it's fast:** standard chained `.add()` normalises after every element (N log10 calls).
540
- * `sumArray` aligns all coefficients to the largest exponent (pivot), sums them,
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
- * For 50 elements: chained add ~ 50 log10 calls + 50 allocations;
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
- let pivotExp = numbers[0].exponent;
558
- for (let i = 1; i < len; i++) {
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
- const diff = pivotExp - n.exponent;
568
- if (diff > cutoff) continue;
569
- total += n.coefficient / pow10Int(diff);
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 = Math.abs(total);
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.createNormalized(total / scale, pivotExp + shift);
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
- * Coefficients are multiplied together with intermediate normalisation after each step
582
- * to keep values in [1, 10). Exponents are summed. One total normalisation at the end.
583
- *
584
- * Empty array returns {@link One}. Single element returned as-is.
585
- *
586
- * @example
587
- * ArbitraryNumber.productArray([an(2), an(3), an(4)]); // 24
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
- const absC = c < 0 ? -c : c;
602
- const shift = Math.floor(Math.log10(absC));
603
- c /= pow10(shift);
604
- e += shift;
801
+ c /= 10;
802
+ e += 1;
605
803
  }
606
804
  }
607
- return _ArbitraryNumber.normalizeFrom(c, e);
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
- * Compares this number to `other`.
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
- * @example
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
- return _ArbitraryNumber.Zero;
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
- return this.coefficient >= 0 ? _ArbitraryNumber.Zero : new _ArbitraryNumber(-1, 0);
839
+ this.coefficient = this.coefficient >= 0 ? 0 : -1;
840
+ this.exponent = 0;
841
+ return this;
706
842
  }
707
- return new _ArbitraryNumber(Math.floor(this.coefficient * pow10(this.exponent)), 0);
843
+ return this._normalizeInto(Math.floor(this.coefficient * pow10(this.exponent)), 0);
708
844
  }
709
845
  /**
710
- * Returns the smallest integer greater than or equal to this number (ceil toward +Infinity).
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
- * @example
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
- return _ArbitraryNumber.Zero;
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
- return this.coefficient > 0 ? _ArbitraryNumber.One : _ArbitraryNumber.Zero;
854
+ const ceiled = this.coefficient > 0 ? 1 : 0;
855
+ this.coefficient = ceiled;
856
+ this.exponent = 0;
857
+ return this;
728
858
  }
729
- return new _ArbitraryNumber(Math.ceil(this.coefficient * pow10(this.exponent)), 0);
859
+ return this._normalizeInto(Math.ceil(this.coefficient * pow10(this.exponent)), 0);
730
860
  }
731
861
  /**
732
- * Clamps `value` to the inclusive range `[min, max]`.
862
+ * Rounds to the nearest integer in-place.
733
863
  *
734
- * @example
735
- * ArbitraryNumber.clamp(new ArbitraryNumber(5, 2), new ArbitraryNumber(1, 3), new ArbitraryNumber(2, 3)); // 1*10^3 (500 clamped to [1000, 2000])
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
- * @param value - The value to clamp.
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
- static min(a, b) {
751
- return a.lessThan(b) ? a : b;
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
- * Returns the larger of `a` and `b`.
755
- * @example ArbitraryNumber.max(a, b)
886
+ * Truncates toward zero in-place.
887
+ *
888
+ * **Mutates `this`. Returns `this`.**
756
889
  */
757
- static max(a, b) {
758
- return a.greaterThan(b) ? a : b;
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
- * Linear interpolation: `a + (b - a) * t` where `t in [0, 1]` is a plain number.
902
+ * Returns √this in-place.
762
903
  *
763
- * Used for smooth animations and tweening in game UIs.
764
- * `t = 0` returns `a`; `t = 1` returns `b`.
904
+ * **Mutates `this`. Returns `this`.**
765
905
  *
766
- * @param t - Interpolation factor as a plain `number`. Values outside [0, 1] are allowed (extrapolation).
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
- static lerp(a, b, t) {
771
- if (t === 0) return a;
772
- if (t === 1) return b;
773
- return a.add(b.sub(a).mul(_ArbitraryNumber.from(t)));
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
- * Runs `fn` with `PrecisionCutoff` temporarily set to `cutoff`, then restores the previous value.
777
- *
778
- * Useful when one section of code needs different precision than the rest.
923
+ * Returns ∛this in-place.
779
924
  *
780
- * @example
781
- * // Run financial calculation with higher precision
782
- * const result = ArbitraryNumber.withPrecision(50, () => a.add(b));
925
+ * **Mutates `this`. Returns `this`.**
783
926
  */
784
- static withPrecision(cutoff, fn) {
785
- const prev = _ArbitraryNumber.PrecisionCutoff;
786
- _ArbitraryNumber.PrecisionCutoff = cutoff;
787
- try {
788
- return fn();
789
- } finally {
790
- _ArbitraryNumber.PrecisionCutoff = prev;
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 JavaScript `number`.
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 this.
959
+ * Returns `log_base(this)` as a plain `number`.
817
960
  *
818
- * Computed as pure coefficient math - no `Math.log10` call. Cost: one `Math.sqrt`.
819
- * For even exponents: `sqrt(c) * 10^(e/2)`.
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)` — the natural logarithm — as a plain JavaScript `number`.
880
- *
881
- * Computed as `log10(this) / log10(e)`.
882
- *
883
- * @throws `"Logarithm of zero is undefined"` when this is zero.
884
- * @throws `"Logarithm of a negative number is undefined"` when this is negative.
885
- *
886
- * @example
887
- * new ArbitraryNumber(1, 0).ln(); // 0
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 an `ArbitraryNumber`, where `n` is a plain JS `number`.
894
- *
895
- * This is the inverse of {@link log10}. Useful for converting a log10 result
896
- * back into an `ArbitraryNumber`.
897
- *
898
- * @example
899
- * ArbitraryNumber.exp10(6); // 1*10^6 (= 1,000,000)
900
- * ArbitraryNumber.exp10(3.5); // 10^3.5 ≈ 3162.3
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
- const c = Math.pow(10, fracPart);
911
- return _ArbitraryNumber.createNormalized(c, intPart);
987
+ return _ArbitraryNumber.unsafe(Math.pow(10, fracPart), intPart);
912
988
  }
989
+ // ── Comparisons (read-only) ────────────────────────────────────────────────
913
990
  /**
914
- * Returns the nearest integer value (rounds half-up).
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
- * @example
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
- round() {
925
- if (this.coefficient === 0) return _ArbitraryNumber.Zero;
926
- if (this.exponent >= _ArbitraryNumber.PrecisionCutoff) return this;
927
- if (this.exponent < 0) {
928
- if (this.exponent <= -2) return _ArbitraryNumber.Zero;
929
- const rounded = Math.round(this.coefficient * 0.1);
930
- return rounded === 0 ? _ArbitraryNumber.Zero : new _ArbitraryNumber(rounded, 0);
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 new _ArbitraryNumber(Math.round(this.coefficient * pow10(this.exponent)), 0);
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
- * Returns the integer part of this number, truncating toward zero.
936
- *
937
- * Unlike `floor()`, which rounds toward −∞, `trunc()` always rounds toward 0:
938
- * - `trunc(1.7) = 1` (same as floor)
939
- * - `trunc(-1.7) = -1` (different from floor, which gives -2)
940
- *
941
- * Numbers with `exponent >= PrecisionCutoff` are already integers and returned unchanged.
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
- * @example
944
- * new ArbitraryNumber(1.7, 0).trunc(); // 1
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
- trunc() {
948
- if (this.coefficient === 0) return _ArbitraryNumber.Zero;
949
- if (this.exponent >= _ArbitraryNumber.PrecisionCutoff) return this;
950
- if (this.exponent < 0) return _ArbitraryNumber.Zero;
951
- return new _ArbitraryNumber(Math.trunc(this.coefficient * pow10(this.exponent)), 0);
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
- * Converts to a plain JavaScript `number`.
1106
+ * Returns a `FrozenArbitraryNumber` wrapping the same value.
966
1107
  *
967
- * Precision is limited to float64 (~15 significant digits).
968
- * Returns `Infinity` for exponents beyond the float64 range (>=308).
969
- * Returns `0` for exponents below the float64 range (<=-324).
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
- * @example
972
- * new ArbitraryNumber(1.5, 3).toNumber(); // 1500
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 raw string representation: `"<coefficient>|<exponent>"`.
1133
+ * Returns a compact string representation: `"<coefficient>|<exponent>"`.
994
1134
  *
995
- * Useful for compact textual serialization. Reconstruct via {@link ArbitraryNumber.parse}.
1135
+ * Shorter than JSON for save-game serialisation. Reconstruct via `ArbitraryNumber.parse`.
996
1136
  *
997
1137
  * @example
998
- * an(1.5, 3).toRaw(); // "1.5|3"
999
- * an(-2, 6).toRaw(); // "-2|6"
1138
+ * an(1500).toRawString() // "1.5|3"
1000
1139
  */
1001
- toRaw() {
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.normalizeFrom(c, e);
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 (exact round-trip): `"1.5|3"`, `"-2.5|-6"`
1035
- * - Standard scientific notation: `"1.5e+3"`, `"1.5e-3"`, `"1.5E3"`, `"1.5e3"`
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
- * @example
1039
- * ArbitraryNumber.parse("1.5|3"); // same as an(1.5, 3)
1040
- * ArbitraryNumber.parse("1.5e+3"); // same as ArbitraryNumber.from(1500)
1041
- * ArbitraryNumber.parse("1500"); // same as ArbitraryNumber.from(1500)
1042
- *
1043
- * @throws `ArbitraryNumberInputError` when the string cannot be parsed or produces a non-finite value.
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.normalizeFrom(c, e);
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
- /** Returns `true` when this number is zero. */
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 {@link scientificNotation} when no plugin is provided.
1109
- * `decimals` controls the number of decimal places passed to the plugin and defaults to `2`.
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 = 2) {
1214
+ toString(notation = scientificNotation, decimals = _ArbitraryNumber.defaults.notationDecimals) {
1120
1215
  return notation.format(this.coefficient, this.exponent, decimals);
1121
1216
  }
1122
1217
  };
1123
- /**
1124
- * Precision cutoff: exponent-difference threshold below which the smaller operand
1125
- * is negligible and silently skipped during addition/subtraction.
1126
- *
1127
- * When |exponent_diff| > PrecisionCutoff, the smaller operand contributes less than
1128
- * 10^-PrecisionCutoff of the result - below float64 coefficient precision for the default of 15.
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
- // src/core/an.ts
1145
- var createAn = ((coefficient, exponent = 0) => new ArbitraryNumber(coefficient, exponent));
1146
- createAn.from = (value) => ArbitraryNumber.from(value);
1147
- var an = createAn;
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
- // ── Construction ────────────────────────────────────────────────────
1155
- /** Creates an `AnChain` from an `ArbitraryNumber` or a plain `number`. */
1156
- static from(value) {
1157
- if (value instanceof ArbitraryNumber) return new _AnChain(value);
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
- // ── Arithmetic ───────────────────────────────────────────────────────
1161
- /** Adds `other` to the accumulated value. */
1162
- add(other) {
1163
- this.value = this.value.add(other);
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
- /** Subtracts `other` from the accumulated value. */
1167
- sub(other) {
1168
- this.value = this.value.sub(other);
1169
- return this;
1247
+ sub(_other) {
1248
+ this._throwMutation("sub");
1170
1249
  }
1171
- /** Multiplies the accumulated value by `other`. */
1172
- mul(other) {
1173
- this.value = this.value.mul(other);
1174
- return this;
1250
+ mul(_other) {
1251
+ this._throwMutation("mul");
1175
1252
  }
1176
- /** Divides the accumulated value by `other`. */
1177
- div(other) {
1178
- this.value = this.value.div(other);
1179
- return this;
1253
+ div(_other) {
1254
+ this._throwMutation("div");
1180
1255
  }
1181
- /** Raises the accumulated value to `exp`. */
1182
- pow(exp) {
1183
- this.value = this.value.pow(exp);
1184
- return this;
1256
+ negate() {
1257
+ this._throwMutation("negate");
1185
1258
  }
1186
- // ── Fused (avoids one intermediate allocation per call) ──────────────
1187
- /** `(this * mult) + add` */
1188
- mulAdd(mult, add) {
1189
- this.value = this.value.mulAdd(mult, add);
1190
- return this;
1259
+ abs() {
1260
+ this._throwMutation("abs");
1191
1261
  }
1192
- /** `(this + add) * mult` */
1193
- addMul(add, mult) {
1194
- this.value = this.value.addMul(add, mult);
1195
- return this;
1262
+ pow(_n) {
1263
+ this._throwMutation("pow");
1196
1264
  }
1197
- /** `(this * mult) - sub` */
1198
- mulSub(mult, sub) {
1199
- this.value = this.value.mulSub(mult, sub);
1200
- return this;
1265
+ mulAdd(_m, _a) {
1266
+ this._throwMutation("mulAdd");
1201
1267
  }
1202
- /** `(this - sub) * mult` */
1203
- subMul(sub, mult) {
1204
- this.value = this.value.subMul(sub, mult);
1205
- return this;
1268
+ addMul(_a, _m) {
1269
+ this._throwMutation("addMul");
1206
1270
  }
1207
- /** `(this / div) + add` */
1208
- divAdd(div, add) {
1209
- this.value = this.value.divAdd(div, add);
1210
- return this;
1271
+ mulSub(_m, _s) {
1272
+ this._throwMutation("mulSub");
1211
1273
  }
1212
- // ── Unary ────────────────────────────────────────────────────────────
1213
- /** Absolute value. */
1214
- abs() {
1215
- this.value = this.value.abs();
1216
- return this;
1274
+ subMul(_s, _m) {
1275
+ this._throwMutation("subMul");
1217
1276
  }
1218
- /** Negates the accumulated value. */
1219
- neg() {
1220
- this.value = this.value.negate();
1221
- return this;
1277
+ divAdd(_d, _a) {
1278
+ this._throwMutation("divAdd");
1222
1279
  }
1223
- /** Square root of the accumulated value. */
1224
- sqrt() {
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.value = this.value.floor();
1231
- return this;
1284
+ this._throwMutation("floor");
1232
1285
  }
1233
- /** Rounds up to the nearest integer. */
1234
1286
  ceil() {
1235
- this.value = this.value.ceil();
1236
- return this;
1287
+ this._throwMutation("ceil");
1237
1288
  }
1238
- /** Rounds to the nearest integer. */
1239
1289
  round() {
1240
- this.value = this.value.round();
1241
- return this;
1290
+ this._throwMutation("round");
1291
+ }
1292
+ trunc() {
1293
+ this._throwMutation("trunc");
1294
+ }
1295
+ sqrt() {
1296
+ this._throwMutation("sqrt");
1242
1297
  }
1243
- // ── Terminal ─────────────────────────────────────────────────────────
1244
- /** Returns the accumulated `ArbitraryNumber` result. */
1245
- done() {
1246
- return this.value;
1298
+ cbrt() {
1299
+ this._throwMutation("cbrt");
1247
1300
  }
1248
1301
  };
1249
- function chain(value) {
1250
- return AnChain.from(value);
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 (avoids one intermediate allocation per call) ───────────────────
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 `neg()` to the pipeline. */
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
- * Runs this formula's pipeline against `value` and returns the result.
1409
+ * Clones `value` once, runs the pipeline against the clone, and returns it.
1367
1410
  *
1368
- * The formula itself is unchanged - call `apply` as many times as needed.
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(baseDamage);
1374
- * const scaled = damageFormula.apply(boostedBase);
1414
+ * const damage = damageFormula.apply(playerAtk); // playerAtk unchanged
1375
1415
  */
1376
1416
  apply(value) {
1377
- let result = value instanceof ArbitraryNumber ? value : ArbitraryNumber.from(value);
1417
+ const result = value instanceof ArbitraryNumber ? value.clone() : ArbitraryNumber.from(value);
1378
1418
  for (const step of this.steps) {
1379
- result = step(result);
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 of a {@link NormalizedNumber}
1638
- * (i.e. has numeric `coefficient` and `exponent` properties).
1690
+ * Returns `true` if `obj` has the shape `{ coefficient: number; exponent: number }`.
1639
1691
  *
1640
- * Note: both `ArbitraryNumber` instances and plain objects with the right
1641
- * shape will pass this check. Use {@link isArbitraryNumber} when you need
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?.coefficient === "number" && typeof obj?.exponent === "number";
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 ArbitraryNumberOps.from(value);
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, AnChain, AnFormula, ArbitraryNumber, ArbitraryNumberDomainError, ArbitraryNumberError, ArbitraryNumberGuard, ArbitraryNumberHelpers, ArbitraryNumberInputError, ArbitraryNumberOps, CLASSIC_UNITS, COMPACT_UNITS, ScientificNotation, SuffixNotationBase, UnitNotation, alphabetSuffix, an, chain, formula, ArbitraryNumberGuard as guard, ArbitraryNumberHelpers as helpers, letterNotation, ArbitraryNumberOps as ops, scientificNotation, unitNotation };
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