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