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.d.cts CHANGED
@@ -1,17 +1,3 @@
1
- /**
2
- * A number stored in normalised scientific notation as `coefficient * 10^exponent`,
3
- * where `1 <= |coefficient| < 10` (or `coefficient === 0`).
4
- *
5
- * @example
6
- * const n: NormalizedNumber = { coefficient: 1.5, exponent: 3 }; // 1500
7
- * const z: NormalizedNumber = { coefficient: 0, exponent: 0 }; // 0
8
- */
9
- interface NormalizedNumber {
10
- /** The significand, always in `[1, 10)` or `0`. */
11
- coefficient: number;
12
- /** The power of 10 by which the coefficient is scaled. */
13
- exponent: number;
14
- }
15
1
  /** The result of a three-way comparison: negative, zero, or positive. */
16
2
  type Signum = -1 | 0 | 1;
17
3
  /** Remainder after dividing an exponent by 3 - the within-tier offset. */
@@ -224,504 +210,415 @@ interface UnitNotationOptions extends SuffixNotationPluginOptions {
224
210
  }
225
211
 
226
212
  /**
227
- * An immutable number with effectively unlimited range, stored as `coefficient * 10^exponent`
213
+ * Global tunables for `ArbitraryNumber`.
214
+ *
215
+ * Mutating these is a process-level change — not per-instance.
216
+ */
217
+ interface ArbitraryNumberDefaults {
218
+ /**
219
+ * Exponent-difference threshold below which the smaller operand is negligible
220
+ * and silently skipped during addition/subtraction.
221
+ *
222
+ * Default: 15 (matches float64 coefficient precision of ~15.95 significant digits).
223
+ */
224
+ scaleCutoff: number;
225
+ /** Default decimal places used by `toString()` when no argument is supplied. */
226
+ notationDecimals: number;
227
+ }
228
+ /**
229
+ * A mutable number with effectively unlimited range, stored as `coefficient * 10^exponent`
228
230
  * in normalised scientific notation.
229
231
  *
230
- * The coefficient is always in `[1, 10)` (or `0`). Addition short-circuits when the exponent
231
- * difference between operands exceeds {@link PrecisionCutoff} - the smaller value is below
232
- * the precision floor of the larger and is silently discarded.
232
+ * The coefficient is always in `[1, 10)` (or `0`). Arithmetic methods **mutate `this`** and
233
+ * return `this` enabling zero-allocation chaining on the hot path:
234
+ *
235
+ * ```ts
236
+ * gold.add(drop).sub(cost).mul(multiplier);
237
+ * ```
238
+ *
239
+ * Call `.clone()` before any operation where you need to preserve the original value.
240
+ *
241
+ * Addition short-circuits when the exponent difference between operands exceeds
242
+ * {@link ArbitraryNumber.defaults.scaleCutoff} — the smaller value is below the precision
243
+ * floor and is silently discarded.
244
+ *
245
+ * **Static = allocate. Instance = mutate.**
246
+ * Static arithmetic methods (`ArbitraryNumber.add`, etc.) always return a new instance.
247
+ * Instance methods (`a.add(b)`) mutate `a` and return `a`.
233
248
  *
234
249
  * @example
235
- * const a = new ArbitraryNumber(1.5, 3); // 1,500
236
- * const b = new ArbitraryNumber(2.5, 3); // 2,500
237
- * a.add(b).toString(); // "4.00e+3"
238
- * a.mul(b).toString(); // "3.75e+6"
250
+ * const gold = new ArbitraryNumber(1.5, 3); // 1_500
251
+ * gold.add(drop).sub(cost).mul(multiplier); // mutates gold
252
+ * const snapshot = gold.clone(); // safe copy
239
253
  */
240
- declare class ArbitraryNumber implements NormalizedNumber {
254
+ declare class ArbitraryNumber {
241
255
  /** The significand, always in `[1, 10)` or `0`. */
242
- readonly coefficient: number;
256
+ coefficient: number;
243
257
  /** The power of 10 by which the coefficient is scaled. */
244
- readonly exponent: number;
258
+ exponent: number;
259
+ /** Global tunables. Mutating these is a process-level change. */
260
+ static readonly defaults: ArbitraryNumberDefaults;
261
+ /** The additive identity: `0`. Frozen — calling mutating methods throws. */
262
+ static readonly Zero: FrozenArbitraryNumber;
263
+ /** The multiplicative identity: `1`. Frozen — calling mutating methods throws. */
264
+ static readonly One: FrozenArbitraryNumber;
265
+ /** `10`. Frozen — calling mutating methods throws. */
266
+ static readonly Ten: FrozenArbitraryNumber;
245
267
  /**
246
- * Precision cutoff: exponent-difference threshold below which the smaller operand
247
- * is negligible and silently skipped during addition/subtraction.
268
+ * Constructs a new `ArbitraryNumber` and immediately normalises it so that
269
+ * `1 <= |coefficient| < 10` (or `coefficient === 0`).
248
270
  *
249
- * When |exponent_diff| > PrecisionCutoff, the smaller operand contributes less than
250
- * 10^-PrecisionCutoff of the result - below float64 coefficient precision for the default of 15.
271
+ * Always two numeric arguments — this keeps the constructor monomorphic so V8
272
+ * locks in a hidden class on first use (~5 ns allocation).
251
273
  *
252
- * Default: 15 (matches float64 coefficient precision of ~15.95 significant digits).
253
- * Game patterns: diffs 0-8 (exact), prestige 15-25 (loss <0.0001%), idle 20-50 (~0.1% loss).
274
+ * @example
275
+ * new ArbitraryNumber(15, 3); // stored as { coefficient: 1.5, exponent: 4 }
254
276
  *
255
- * Override globally via assignment, or use {@link withPrecision} for a scoped block.
277
+ * @throws `ArbitraryNumberInputError` for `NaN`/`Infinity` coefficient or exponent.
256
278
  */
257
- static PrecisionCutoff: number;
258
- /** The additive identity: `0`. */
259
- static readonly Zero: ArbitraryNumber;
260
- /** The multiplicative identity: `1`. */
261
- static readonly One: ArbitraryNumber;
262
- /** `10`. */
263
- static readonly Ten: ArbitraryNumber;
279
+ constructor(coefficient: number, exponent: number);
280
+ /**
281
+ * @internal Fast-path factory for already-normalised values. Not exported from `index.ts`.
282
+ *
283
+ * Allocates a new instance and writes the two fields directly — bypasses validation
284
+ * and normalisation. Only valid when `|coefficient|` is already in `[1, 10)` (or 0)
285
+ * and `exponent` is correct.
286
+ */
287
+ static unsafe(coefficient: number, exponent: number): ArbitraryNumber;
288
+ /** @internal Normalise an arbitrary `(c, e)` pair into a new instance. */
289
+ private static _normalizeNew;
290
+ /** @internal Normalise `(c, e)` into `this` (mutates). Returns `this`. */
291
+ private _normalizeInto;
292
+ /**
293
+ * Returns a fresh, unfrozen copy of this number. The canonical way to preserve
294
+ * a value before mutating it:
295
+ *
296
+ * ```ts
297
+ * const before = gold.clone();
298
+ * gold.add(drop);
299
+ * ```
300
+ */
301
+ clone(): ArbitraryNumber;
264
302
  /**
265
303
  * Creates an `ArbitraryNumber` from a plain JavaScript `number`.
266
304
  *
267
- * Prefer this over `new ArbitraryNumber(value, 0)` when working with
268
- * ordinary numeric literals - it reads clearly at the call site and
269
- * validates the input.
305
+ * @throws `ArbitraryNumberInputError` for `NaN`, `Infinity`, or `-Infinity`.
270
306
  *
271
307
  * @example
272
- * ArbitraryNumber.from(1500); // { coefficient: 1.5, exponent: 3 }
273
- * ArbitraryNumber.from(0.005); // { coefficient: 5, exponent: -3 }
274
- * ArbitraryNumber.from(0); // ArbitraryNumber.Zero
275
- * ArbitraryNumber.from(-0); // ArbitraryNumber.Zero (signed zero is normalised to +0)
276
- *
277
- * @param value - Any finite number. Signed zero (`-0`) is treated as `0`.
278
- * @throws `"ArbitraryNumber.from: value must be finite"` for `NaN`, `Infinity`, or `-Infinity`.
308
+ * ArbitraryNumber.from(1500); // { coefficient: 1.5, exponent: 3 }
279
309
  */
280
310
  static from(value: number): ArbitraryNumber;
281
311
  /**
282
- * Constructs a new `ArbitraryNumber` and immediately normalises it so that
283
- * `1 <= |coefficient| < 10` (or `coefficient === 0`).
312
+ * Like `from`, but returns `null` instead of throwing for non-finite inputs.
313
+ *
314
+ * Use at system boundaries (form inputs, external APIs) where bad input should
315
+ * be handled gracefully.
284
316
  *
285
317
  * @example
286
- * new ArbitraryNumber(15, 3); // stored as { coefficient: 1.5, exponent: 4 }
318
+ * ArbitraryNumber.tryFrom(Infinity) // null
319
+ * ArbitraryNumber.tryFrom(1500) // ArbitraryNumber { coefficient: 1.5, exponent: 3 }
320
+ */
321
+ static tryFrom(value: number): Nullable<ArbitraryNumber>;
322
+ /**
323
+ * Returns a **new** instance equal to `a + b`.
287
324
  *
288
- * @param coefficient - The significand. Must be a finite number; will be normalised.
289
- * @param exponent - The power of 10. Must be a finite number.
290
- * @throws `"ArbitraryNumber: coefficient must be finite"` for `NaN`, `Infinity`, or `-Infinity`.
291
- * @throws `"ArbitraryNumber: exponent must be finite"` for non-finite exponents.
325
+ * Static methods always allocate use instance `.add()` on hot paths.
292
326
  */
293
- constructor(coefficient: number, exponent: number);
327
+ static add(a: ArbitraryNumber, b: ArbitraryNumber): ArbitraryNumber;
328
+ /**
329
+ * Returns a **new** instance equal to `a - b`.
330
+ */
331
+ static sub(a: ArbitraryNumber, b: ArbitraryNumber): ArbitraryNumber;
332
+ /**
333
+ * Returns a **new** instance equal to `a * b`.
334
+ */
335
+ static mul(a: ArbitraryNumber, b: ArbitraryNumber): ArbitraryNumber;
294
336
  /**
295
- * @internal Fast-path factory for already-normalised values.
337
+ * Returns a **new** instance equal to `a / b`.
296
338
  *
297
- * Uses Object.create() to bypass the constructor (zero normalisation cost).
298
- * Only valid when |coefficient| is already in [1, 10) and exponent is correct.
299
- * Do NOT use for unnormalised inputs - call new ArbitraryNumber(c, e) instead.
339
+ * @throws `"Division by zero"` when `b` is zero.
300
340
  */
301
- private static createNormalized;
302
- private static normalizeFrom;
341
+ static div(a: ArbitraryNumber, b: ArbitraryNumber): ArbitraryNumber;
303
342
  /**
304
- * Returns `this + other`.
343
+ * Adds `other` to this number in-place.
305
344
  *
306
- * When the exponent difference exceeds {@link PrecisionCutoff}, the smaller
307
- * operand has no effect and the larger is returned as-is.
345
+ * When the exponent difference exceeds `defaults.scaleCutoff`, the smaller
346
+ * operand has no effect and `this` is returned unchanged.
347
+ *
348
+ * **Mutates `this`. Returns `this`.**
308
349
  *
309
350
  * @example
310
- * new ArbitraryNumber(1.5, 3).add(new ArbitraryNumber(2.5, 3)); // 4*10^3
351
+ * gold.add(drop); // gold is mutated
311
352
  */
312
- add(other: ArbitraryNumber): ArbitraryNumber;
353
+ add(other: ArbitraryNumber): this;
313
354
  /**
314
- * Returns `this - other`.
355
+ * Subtracts `other` from this number in-place.
315
356
  *
316
- * @example
317
- * new ArbitraryNumber(3.5, 3).sub(new ArbitraryNumber(1.5, 3)); // 2*10^3
357
+ * **Mutates `this`. Returns `this`.**
318
358
  */
319
- sub(other: ArbitraryNumber): ArbitraryNumber;
359
+ sub(other: ArbitraryNumber): this;
320
360
  /**
321
- * Returns `this * other`.
361
+ * Multiplies this number by `other` in-place.
322
362
  *
323
- * @example
324
- * new ArbitraryNumber(2, 3).mul(new ArbitraryNumber(3, 4)); // 6*10^7
363
+ * **Mutates `this`. Returns `this`.**
325
364
  */
326
- mul(other: ArbitraryNumber): ArbitraryNumber;
365
+ mul(other: ArbitraryNumber): this;
327
366
  /**
328
- * Returns `this / other`.
367
+ * Divides this number by `other` in-place.
329
368
  *
330
- * @example
331
- * new ArbitraryNumber(6, 7).div(new ArbitraryNumber(3, 4)); // 2*10^3
369
+ * **Mutates `this`. Returns `this`.**
332
370
  *
333
371
  * @throws `"Division by zero"` when `other` is zero.
334
372
  */
335
- div(other: ArbitraryNumber): ArbitraryNumber;
373
+ div(other: ArbitraryNumber): this;
336
374
  /**
337
- * Returns the arithmetic negation of this number (`-this`).
375
+ * Negates this number in-place.
338
376
  *
339
- * @example
340
- * new ArbitraryNumber(1.5, 3).negate(); // -1.5*10^3
377
+ * **Mutates `this`. Returns `this`.**
341
378
  */
342
- negate(): ArbitraryNumber;
379
+ negate(): this;
343
380
  /**
344
- * Returns the absolute value of this number (`|this|`).
345
- *
346
- * Returns `this` unchanged when the number is already non-negative.
381
+ * Sets this number to its absolute value in-place.
347
382
  *
348
- * @example
349
- * new ArbitraryNumber(-1.5, 3).abs(); // 1.5*10^3
383
+ * **Mutates `this`. Returns `this`.**
350
384
  */
351
- abs(): ArbitraryNumber;
385
+ abs(): this;
352
386
  /**
353
- * Returns `this^n`.
387
+ * Raises this number to the power `n` in-place.
354
388
  *
355
389
  * Supports integer, fractional, and negative exponents.
356
- * `x^0` always returns {@link One}, including `0^0` (by convention).
390
+ * `x^0` always sets `this` to `1`, including `0^0` (by convention).
357
391
  *
358
- * @example
359
- * new ArbitraryNumber(2, 3).pow(2); // 4*10^6
360
- * new ArbitraryNumber(2, 0).pow(-1); // 5*10^-1 (= 0.5)
392
+ * **Mutates `this`. Returns `this`.**
361
393
  *
362
- * @param n - The exponent to raise this number to.
363
394
  * @throws `"Zero cannot be raised to a negative power"` when this is zero and `n < 0`.
364
395
  */
365
- pow(n: number): ArbitraryNumber;
396
+ pow(n: number): this;
366
397
  /**
367
- * Fused multiply-add: `(this * multiplier) + addend`.
368
- *
369
- * Faster than `.mul(multiplier).add(addend)` because it avoids allocating an
370
- * intermediate ArbitraryNumber for the product. One normalisation pass total.
398
+ * Fused multiply-add in-place: `this = (this * multiplier) + addend`.
371
399
  *
372
- * Common pattern - prestige loop: `value = value.mulAdd(prestigeMultiplier, prestigeBoost)`
400
+ * Faster than `.mul(multiplier).add(addend)` one normalisation pass total, no
401
+ * intermediate allocation.
373
402
  *
374
- * @example
375
- * // Equivalent to value.mul(mult).add(boost) but ~35-50% faster
376
- * const prestiged = currentValue.mulAdd(multiplier, boost);
403
+ * **Mutates `this`. Returns `this`.**
377
404
  */
378
- mulAdd(multiplier: ArbitraryNumber, addend: ArbitraryNumber): ArbitraryNumber;
405
+ mulAdd(multiplier: ArbitraryNumber, addend: ArbitraryNumber): this;
379
406
  /**
380
- * Fused add-multiply: `(this + addend) * multiplier`.
381
- *
382
- * Faster than `.add(addend).mul(multiplier)` because it avoids allocating an
383
- * intermediate ArbitraryNumber for the sum. One normalisation pass total.
384
- *
385
- * Common pattern - upgrade calculation: `newValue = baseValue.addMul(bonus, multiplier)`
407
+ * @internal
408
+ * Computes `(this + sign * addendC * 10^addendE)` and writes the normalised
409
+ * result back into `this`. Used by `addMul` and `subMul` to share the alignment
410
+ * logic without duplication.
386
411
  *
387
- * @example
388
- * // Equivalent to base.add(bonus).mul(multiplier) but ~20-25% faster
389
- * const upgraded = baseValue.addMul(bonus, multiplier);
412
+ * Returns the sign-adjusted addend coefficient so callers can detect the
413
+ * zero-result case. Writes the intermediate normalised sum into `this.coefficient`
414
+ * / `this.exponent` ready for the subsequent multiply step.
390
415
  */
391
- addMul(addend: ArbitraryNumber, multiplier: ArbitraryNumber): ArbitraryNumber;
416
+ private _addScaledInto;
392
417
  /**
393
- * Fused multiply-subtract: `(this * multiplier) - subtrahend`.
394
- *
395
- * Avoids one intermediate allocation vs `.mul(multiplier).sub(subtrahend)`.
418
+ * Fused add-multiply in-place: `this = (this + addend) * multiplier`.
396
419
  *
397
- * Common pattern - resource drain: `income.mulSub(rate, upkeepCost)`
420
+ * **Mutates `this`. Returns `this`.**
398
421
  */
399
- mulSub(multiplier: ArbitraryNumber, subtrahend: ArbitraryNumber): ArbitraryNumber;
422
+ addMul(addend: ArbitraryNumber, multiplier: ArbitraryNumber): this;
400
423
  /**
401
- * Fused subtract-multiply: `(this - subtrahend) * multiplier`.
402
- *
403
- * Avoids one intermediate allocation vs `.sub(subtrahend).mul(multiplier)`.
424
+ * Fused multiply-subtract in-place: `this = (this * multiplier) - subtrahend`.
404
425
  *
405
- * Common pattern - upgrade after penalty: `health.subMul(damage, multiplier)`
426
+ * **Mutates `this`. Returns `this`.**
406
427
  */
407
- subMul(subtrahend: ArbitraryNumber, multiplier: ArbitraryNumber): ArbitraryNumber;
428
+ mulSub(multiplier: ArbitraryNumber, subtrahend: ArbitraryNumber): this;
408
429
  /**
409
- * Fused divide-add: `(this / divisor) + addend`.
430
+ * Fused subtract-multiply in-place: `this = (this - subtrahend) * multiplier`.
410
431
  *
411
- * Avoids one intermediate allocation vs `.div(divisor).add(addend)`.
432
+ * **Mutates `this`. Returns `this`.**
433
+ */
434
+ subMul(subtrahend: ArbitraryNumber, multiplier: ArbitraryNumber): this;
435
+ /**
436
+ * Fused divide-add in-place: `this = (this / divisor) + addend`.
412
437
  *
413
- * Common pattern - efficiency bonus: `damage.divAdd(armor, flat)`
438
+ * **Mutates `this`. Returns `this`.**
414
439
  *
415
440
  * @throws `"Division by zero"` when divisor is zero.
416
441
  */
417
- divAdd(divisor: ArbitraryNumber, addend: ArbitraryNumber): ArbitraryNumber;
442
+ divAdd(divisor: ArbitraryNumber, addend: ArbitraryNumber): this;
418
443
  /**
419
- * Fused multiply-divide: `(this * multiplier) / divisor`.
444
+ * Fused multiply-divide in-place: `this = (this * multiplier) / divisor`.
420
445
  *
421
- * Avoids one intermediate allocation vs `.mul(multiplier).div(divisor)`.
422
- * The divisor zero-check is performed before the multiply to avoid unnecessary work.
423
- *
424
- * Common pattern - idle-tick math: `(production * deltaTime) / cost`
425
- *
426
- * @example
427
- * // Equivalent to production.mul(deltaTime).div(cost) but ~30-40% faster
428
- * const consumed = production.mulDiv(deltaTime, cost);
446
+ * **Mutates `this`. Returns `this`.**
429
447
  *
430
448
  * @throws `"Division by zero"` when divisor is zero.
431
449
  */
432
- mulDiv(multiplier: ArbitraryNumber, divisor: ArbitraryNumber): ArbitraryNumber;
450
+ mulDiv(multiplier: ArbitraryNumber, divisor: ArbitraryNumber): this;
433
451
  /**
434
- * Efficiently sums an array of ArbitraryNumbers in a single normalisation pass.
435
- *
436
- * **Why it's fast:** standard chained `.add()` normalises after every element (N log10 calls).
437
- * `sumArray` aligns all coefficients to the largest exponent (pivot), sums them,
438
- * then normalises once - regardless of array size.
439
- *
440
- * For 50 elements: chained add ~ 50 log10 calls + 50 allocations;
441
- * sumArray ~ 50 divisions + 1 log10 call + 1 allocation -> ~9* faster.
452
+ * Efficiently sums an array of `ArbitraryNumber`s in a single pass.
442
453
  *
443
- * Common pattern - income aggregation: `total = ArbitraryNumber.sumArray(incomeSourcesPerTick)`
454
+ * Maintains a running pivot exponent and rescales the accumulator when a larger
455
+ * exponent is encountered — one pass, no pre-scan needed.
444
456
  *
445
- * @example
446
- * const total = ArbitraryNumber.sumArray(incomeSources); // far faster than .reduce((a, b) => a.add(b))
447
- *
448
- * @param numbers - Array to sum. Empty array returns {@link Zero}. Single element returned as-is.
457
+ * Empty array returns `Zero`. Single element returned as-is (no clone).
449
458
  */
450
459
  static sumArray(numbers: ArbitraryNumber[]): ArbitraryNumber;
451
460
  /**
452
461
  * Multiplies an array of `ArbitraryNumber`s in a single pass.
453
462
  *
454
- * Coefficients are multiplied together with intermediate normalisation after each step
455
- * to keep values in [1, 10). Exponents are summed. One total normalisation at the end.
456
- *
457
- * Empty array returns {@link One}. Single element returned as-is.
458
- *
459
- * @example
460
- * ArbitraryNumber.productArray([an(2), an(3), an(4)]); // 24
463
+ * Empty array returns `One`. Single element returned as-is.
461
464
  */
462
465
  static productArray(numbers: ArbitraryNumber[]): ArbitraryNumber;
463
466
  /**
464
- * Returns the largest value in an array.
465
- *
466
- * Empty array returns {@link Zero}. Single element returned as-is.
467
- *
468
- * @example
469
- * ArbitraryNumber.maxOfArray([an(1), an(3), an(2)]); // an(3)
467
+ * Returns the largest value in an array. Empty array returns `Zero`.
470
468
  */
471
469
  static maxOfArray(numbers: ArbitraryNumber[]): ArbitraryNumber;
472
470
  /**
473
- * Returns the smallest value in an array.
474
- *
475
- * Empty array returns {@link Zero}. Single element returned as-is.
476
- *
477
- * @example
478
- * ArbitraryNumber.minOfArray([an(3), an(1), an(2)]); // an(1)
471
+ * Returns the smallest value in an array. Empty array returns `Zero`.
479
472
  */
480
473
  static minOfArray(numbers: ArbitraryNumber[]): ArbitraryNumber;
481
474
  /**
482
- * Compares this number to `other`.
483
- *
484
- * @returns `1` if `this > other`, `-1` if `this < other`, `0` if equal.
475
+ * Rounds down to the nearest integer in-place (floor toward −∞).
485
476
  *
486
- * @example
487
- * new ArbitraryNumber(1, 4).compareTo(new ArbitraryNumber(9, 3)); // 1 (10000 > 9000)
488
- * new ArbitraryNumber(-1, 4).compareTo(new ArbitraryNumber(1, 3)); // -1 (-10000 < 1000)
477
+ * **Mutates `this`. Returns `this`.**
489
478
  */
490
- compareTo(other: ArbitraryNumber): number;
491
- /** Returns `true` if `this > other`. */
492
- greaterThan(other: ArbitraryNumber): boolean;
493
- /** Returns `true` if `this < other`. */
494
- lessThan(other: ArbitraryNumber): boolean;
495
- /** Returns `true` if `this >= other`. */
496
- greaterThanOrEqual(other: ArbitraryNumber): boolean;
497
- /** Returns `true` if `this <= other`. */
498
- lessThanOrEqual(other: ArbitraryNumber): boolean;
499
- /** Returns `true` if `this === other` in value. */
500
- equals(other: ArbitraryNumber): boolean;
479
+ floor(): this;
501
480
  /**
502
- * Returns the largest integer less than or equal to this number (floor toward -Infinity).
481
+ * Rounds up to the nearest integer in-place (ceil toward +∞).
503
482
  *
504
- * Numbers with `exponent >= PrecisionCutoff` are already integers at that scale
505
- * and are returned unchanged.
506
- *
507
- * @example
508
- * new ArbitraryNumber(1.7, 0).floor(); // 1
509
- * new ArbitraryNumber(-1.7, 0).floor(); // -2
483
+ * **Mutates `this`. Returns `this`.**
510
484
  */
511
- floor(): ArbitraryNumber;
485
+ ceil(): this;
512
486
  /**
513
- * Returns the smallest integer greater than or equal to this number (ceil toward +Infinity).
487
+ * Rounds to the nearest integer in-place.
514
488
  *
515
- * Numbers with `exponent >= PrecisionCutoff` are already integers at that scale
516
- * and are returned unchanged.
489
+ * Uses `Math.round` semantics: half-values round toward positive infinity
490
+ * (`0.5 1`, `-0.5 → 0`). This matches JavaScript's built-in convention.
517
491
  *
518
- * @example
519
- * new ArbitraryNumber(1.2, 0).ceil(); // 2
520
- * new ArbitraryNumber(-1.7, 0).ceil(); // -1
492
+ * **Mutates `this`. Returns `this`.**
521
493
  */
522
- ceil(): ArbitraryNumber;
494
+ round(): this;
523
495
  /**
524
- * Clamps `value` to the inclusive range `[min, max]`.
525
- *
526
- * @example
527
- * ArbitraryNumber.clamp(new ArbitraryNumber(5, 2), new ArbitraryNumber(1, 3), new ArbitraryNumber(2, 3)); // 1*10^3 (500 clamped to [1000, 2000])
496
+ * Truncates toward zero in-place.
528
497
  *
529
- * @param value - The value to clamp.
530
- * @param min - Lower bound (inclusive).
531
- * @param max - Upper bound (inclusive).
498
+ * **Mutates `this`. Returns `this`.**
532
499
  */
533
- static clamp(value: ArbitraryNumber, min: ArbitraryNumber, max: ArbitraryNumber): ArbitraryNumber;
500
+ trunc(): this;
534
501
  /**
535
- * Returns the smaller of `a` and `b`.
536
- * @example ArbitraryNumber.min(a, b)
537
- */
538
- static min(a: ArbitraryNumber, b: ArbitraryNumber): ArbitraryNumber;
539
- /**
540
- * Returns the larger of `a` and `b`.
541
- * @example ArbitraryNumber.max(a, b)
542
- */
543
- static max(a: ArbitraryNumber, b: ArbitraryNumber): ArbitraryNumber;
544
- /**
545
- * Linear interpolation: `a + (b - a) * t` where `t in [0, 1]` is a plain number.
502
+ * Returns √this in-place.
546
503
  *
547
- * Used for smooth animations and tweening in game UIs.
548
- * `t = 0` returns `a`; `t = 1` returns `b`.
504
+ * **Mutates `this`. Returns `this`.**
549
505
  *
550
- * @param t - Interpolation factor as a plain `number`. Values outside [0, 1] are allowed (extrapolation).
551
- * @example
552
- * ArbitraryNumber.lerp(an(100), an(200), 0.5); // 150
506
+ * @throws `"Square root of negative number"` when this is negative.
553
507
  */
554
- static lerp(a: ArbitraryNumber, b: ArbitraryNumber, t: number): ArbitraryNumber;
508
+ sqrt(): this;
555
509
  /**
556
- * Runs `fn` with `PrecisionCutoff` temporarily set to `cutoff`, then restores the previous value.
557
- *
558
- * Useful when one section of code needs different precision than the rest.
510
+ * Returns ∛this in-place.
559
511
  *
560
- * @example
561
- * // Run financial calculation with higher precision
562
- * const result = ArbitraryNumber.withPrecision(50, () => a.add(b));
512
+ * **Mutates `this`. Returns `this`.**
563
513
  */
564
- static withPrecision<T>(cutoff: number, fn: () => T): T;
514
+ cbrt(): this;
565
515
  /**
566
- * Returns `log10(this)` as a plain JavaScript `number`.
567
- *
568
- * Because the number is stored as `c * 10^e`, this is computed exactly as
569
- * `log10(c) + e` - no precision loss from the exponent.
570
- *
571
- * @example
572
- * new ArbitraryNumber(1, 6).log10(); // 6
573
- * new ArbitraryNumber(1.5, 3).log10(); // log10(1.5) + 3 ~ 3.176
516
+ * Returns `log10(this)` as a plain `number`.
574
517
  *
575
518
  * @throws `"Logarithm of zero is undefined"` when this is zero.
576
519
  * @throws `"Logarithm of a negative number is undefined"` when this is negative.
577
520
  */
578
521
  log10(): number;
579
522
  /**
580
- * Returns this.
581
- *
582
- * Computed as pure coefficient math - no `Math.log10` call. Cost: one `Math.sqrt`.
583
- * For even exponents: `sqrt(c) * 10^(e/2)`.
584
- * For odd exponents: `sqrt(c * 10) * 10^((e-1)/2)`.
585
- *
586
- * @throws `"Square root of negative number"` when this is negative.
587
- * @example
588
- * new ArbitraryNumber(4, 0).sqrt(); // 2
589
- * new ArbitraryNumber(1, 4).sqrt(); // 1*10^2 (= 100)
590
- */
591
- sqrt(): ArbitraryNumber;
592
- /**
593
- * Returns the cube root of this number: `∛this`.
594
- *
595
- * Computed without `Math.log10`. For exponents divisible by 3: `cbrt(c) * 10^(e/3)`.
596
- * For remainder 1: `cbrt(c * 10) * 10^((e-1)/3)`.
597
- * For remainder 2: `cbrt(c * 100) * 10^((e-2)/3)`.
598
- *
599
- * Supports negative numbers (cube root of a negative is negative).
600
- *
601
- * @example
602
- * new ArbitraryNumber(8, 0).cbrt(); // 2
603
- * new ArbitraryNumber(1, 9).cbrt(); // 1*10^3 (= 1,000)
604
- * new ArbitraryNumber(-8, 0).cbrt(); // -2
605
- */
606
- cbrt(): ArbitraryNumber;
607
- /**
608
- * Returns `log_base(this)` as a plain JavaScript `number`.
609
- *
610
- * Computed as `log10(this) / log10(base)`. The numerator is exact due to the
611
- * stored `coefficient × 10^exponent` form.
523
+ * Returns `log_base(this)` as a plain `number`.
612
524
  *
613
- * @param base - The logarithm base. Must be positive and not 1.
614
- * @throws `"Logarithm of zero is undefined"` when this is zero.
615
- * @throws `"Logarithm of a negative number is undefined"` when this is negative.
525
+ * @param base - Must be positive and not 1.
616
526
  * @throws `"Logarithm base must be positive and not 1"` for invalid base.
617
- *
618
- * @example
619
- * new ArbitraryNumber(8, 0).log(2); // 3
620
- * new ArbitraryNumber(1, 6).log(10); // 6
621
527
  */
622
528
  log(base: number): number;
623
529
  /**
624
- * Returns `ln(this)` — the natural logarithm — as a plain JavaScript `number`.
625
- *
626
- * Computed as `log10(this) / log10(e)`.
627
- *
628
- * @throws `"Logarithm of zero is undefined"` when this is zero.
629
- * @throws `"Logarithm of a negative number is undefined"` when this is negative.
630
- *
631
- * @example
632
- * new ArbitraryNumber(1, 0).ln(); // 0
530
+ * Returns `ln(this)` as a plain `number`.
633
531
  */
634
532
  ln(): number;
635
533
  /**
636
- * Returns `10^n` as an `ArbitraryNumber`, where `n` is a plain JS `number`.
637
- *
638
- * This is the inverse of {@link log10}. Useful for converting a log10 result
639
- * back into an `ArbitraryNumber`.
640
- *
641
- * @example
642
- * ArbitraryNumber.exp10(6); // 1*10^6 (= 1,000,000)
643
- * ArbitraryNumber.exp10(3.5); // 10^3.5 ≈ 3162.3
534
+ * Returns `10^n` as a new `ArbitraryNumber`.
644
535
  *
645
536
  * @throws `"ArbitraryNumber.exp10: n must be finite"` for non-finite `n`.
646
537
  */
647
538
  static exp10(n: number): ArbitraryNumber;
648
539
  /**
649
- * Returns the nearest integer value (rounds half-up).
650
- *
651
- * Numbers with `exponent >= PrecisionCutoff` are already integers at that scale
652
- * and are returned unchanged.
540
+ * Compares this number to `other`.
653
541
  *
654
- * @example
655
- * new ArbitraryNumber(1.5, 0).round(); // 2
656
- * new ArbitraryNumber(1.4, 0).round(); // 1
657
- * new ArbitraryNumber(-1.5, 0).round(); // -1 (half-up toward positive infinity)
542
+ * @returns `1` if `this > other`, `-1` if `this < other`, `0` if equal.
658
543
  */
659
- round(): ArbitraryNumber;
544
+ compareTo(other: ArbitraryNumber): number;
545
+ /** Returns `true` if `this > other`. */
546
+ greaterThan(other: ArbitraryNumber): boolean;
547
+ /** Returns `true` if `this < other`. */
548
+ lessThan(other: ArbitraryNumber): boolean;
549
+ /** Returns `true` if `this >= other`. */
550
+ greaterThanOrEqual(other: ArbitraryNumber): boolean;
551
+ /** Returns `true` if `this <= other`. */
552
+ lessThanOrEqual(other: ArbitraryNumber): boolean;
553
+ /** Returns `true` if `this === other` in value. */
554
+ equals(other: ArbitraryNumber): boolean;
660
555
  /**
661
- * Returns the integer part of this number, truncating toward zero.
662
- *
663
- * Unlike `floor()`, which rounds toward −∞, `trunc()` always rounds toward 0:
664
- * - `trunc(1.7) = 1` (same as floor)
665
- * - `trunc(-1.7) = -1` (different from floor, which gives -2)
666
- *
667
- * Numbers with `exponent >= PrecisionCutoff` are already integers and returned unchanged.
556
+ * Clamps `value` to the inclusive range `[min, max]`. Returns one of the three
557
+ * inputs (no allocation).
558
+ */
559
+ static clamp(value: ArbitraryNumber, min: ArbitraryNumber, max: ArbitraryNumber): ArbitraryNumber;
560
+ /** Returns the smaller of `a` and `b`. */
561
+ static min(a: ArbitraryNumber, b: ArbitraryNumber): ArbitraryNumber;
562
+ /** Returns the larger of `a` and `b`. */
563
+ static max(a: ArbitraryNumber, b: ArbitraryNumber): ArbitraryNumber;
564
+ /**
565
+ * Linear interpolation: `a + (b - a) * t`.
668
566
  *
669
- * @example
670
- * new ArbitraryNumber(1.7, 0).trunc(); // 1
671
- * new ArbitraryNumber(-1.7, 0).trunc(); // -1
567
+ * Returns `a` unchanged when `t === 0`, `b` unchanged when `t === 1`.
568
+ * All other values of `t` allocate and return a fresh instance.
672
569
  */
673
- trunc(): ArbitraryNumber;
570
+ static lerp(a: ArbitraryNumber, b: ArbitraryNumber, t: number): ArbitraryNumber;
571
+ /**
572
+ * Runs `fn` with `defaults.scaleCutoff` temporarily set to `cutoff`, then restores it.
573
+ */
574
+ static withPrecision<T>(cutoff: number, fn: () => T): T;
575
+ /** Returns `true` when this number is zero. */
576
+ isZero(): boolean;
577
+ /** Returns `true` when this number is strictly positive. */
578
+ isPositive(): boolean;
579
+ /** Returns `true` when this number is strictly negative. */
580
+ isNegative(): boolean;
581
+ /**
582
+ * Returns `true` when this number has no fractional part.
583
+ * Numbers with `exponent >= scaleCutoff` are always considered integers.
584
+ */
585
+ isInteger(): boolean;
674
586
  /**
675
587
  * Returns `1` if positive, `-1` if negative, `0` if zero.
676
- *
677
- * @example
678
- * new ArbitraryNumber(1.5, 3).sign(); // 1
679
- * new ArbitraryNumber(-1.5, 3).sign(); // -1
680
- * ArbitraryNumber.Zero.sign(); // 0
681
588
  */
682
589
  sign(): Signum;
683
590
  /**
684
- * Converts to a plain JavaScript `number`.
591
+ * Returns a `FrozenArbitraryNumber` wrapping the same value.
685
592
  *
686
- * Precision is limited to float64 (~15 significant digits).
687
- * Returns `Infinity` for exponents beyond the float64 range (>=308).
688
- * Returns `0` for exponents below the float64 range (<=-324).
593
+ * Mutating methods on the frozen instance throw `ArbitraryNumberMutationError`.
594
+ * Call `.clone()` on the frozen instance to get a fresh, mutable copy.
595
+ */
596
+ freeze(): FrozenArbitraryNumber;
597
+ /**
598
+ * Converts to a plain JavaScript `number`.
689
599
  *
690
- * @example
691
- * new ArbitraryNumber(1.5, 3).toNumber(); // 1500
692
- * new ArbitraryNumber(1, 400).toNumber(); // Infinity
600
+ * Returns `Infinity` for exponents beyond float64 range (>=308).
601
+ * Returns `0` for exponents below float64 range (<=-324).
693
602
  */
694
603
  toNumber(): number;
695
604
  /**
696
605
  * Returns a stable, minimal JSON representation: `{ c: number, e: number }`.
697
606
  *
698
- * The keys `c` and `e` are intentionally short to keep save-game blobs small.
699
- * Reconstruct via {@link ArbitraryNumber.fromJSON}.
700
- *
701
607
  * Round-trip guarantee: `ArbitraryNumber.fromJSON(x.toJSON()).equals(x)` is always `true`.
702
- *
703
- * @example
704
- * JSON.stringify(an(1.5, 6)); // '{"c":1.5,"e":6}'
705
608
  */
706
609
  toJSON(): ArbitraryNumberJson;
707
610
  /**
708
- * Returns a raw string representation: `"<coefficient>|<exponent>"`.
611
+ * Returns a compact string representation: `"<coefficient>|<exponent>"`.
709
612
  *
710
- * Useful for compact textual serialization. Reconstruct via {@link ArbitraryNumber.parse}.
613
+ * Shorter than JSON for save-game serialisation. Reconstruct via `ArbitraryNumber.parse`.
711
614
  *
712
615
  * @example
713
- * an(1.5, 3).toRaw(); // "1.5|3"
714
- * an(-2, 6).toRaw(); // "-2|6"
616
+ * an(1500).toRawString() // "1.5|3"
715
617
  */
716
- toRaw(): string;
618
+ toRawString(): string;
717
619
  /**
718
620
  * Reconstructs an `ArbitraryNumber` from a `toJSON()` blob.
719
621
  *
720
- * @example
721
- * const n = an(1.5, 6);
722
- * ArbitraryNumber.fromJSON(n.toJSON()).equals(n); // true
723
- *
724
- * @param obj - Object with numeric `c` (coefficient) and `e` (exponent) fields.
725
622
  * @throws `ArbitraryNumberInputError` when the object shape is invalid or values are non-finite.
726
623
  */
727
624
  static fromJSON(obj: unknown): ArbitraryNumber;
@@ -729,52 +626,61 @@ declare class ArbitraryNumber implements NormalizedNumber {
729
626
  * Parses a string into an `ArbitraryNumber`.
730
627
  *
731
628
  * Accepted formats:
732
- * - Raw pipe format (exact round-trip): `"1.5|3"`, `"-2.5|-6"`
733
- * - Standard scientific notation: `"1.5e+3"`, `"1.5e-3"`, `"1.5E3"`, `"1.5e3"`
629
+ * - Raw pipe format: `"1.5|3"`, `"-2.5|-6"`
630
+ * - Scientific notation: `"1.5e+3"`, `"1.5E3"`
734
631
  * - Plain decimal: `"1500"`, `"-0.003"`, `"0"`
735
632
  *
736
- * @example
737
- * ArbitraryNumber.parse("1.5|3"); // same as an(1.5, 3)
738
- * ArbitraryNumber.parse("1.5e+3"); // same as ArbitraryNumber.from(1500)
739
- * ArbitraryNumber.parse("1500"); // same as ArbitraryNumber.from(1500)
740
- *
741
- * @throws `ArbitraryNumberInputError` when the string cannot be parsed or produces a non-finite value.
633
+ * @throws `ArbitraryNumberInputError` for invalid or non-finite input.
742
634
  */
743
635
  static parse(s: string): ArbitraryNumber;
744
- /** Returns `true` when this number is zero. */
745
- isZero(): boolean;
746
- /** Returns `true` when this number is strictly positive. */
747
- isPositive(): boolean;
748
- /** Returns `true` when this number is strictly negative. */
749
- isNegative(): boolean;
750
- /**
751
- * Returns `true` when this number has no fractional part.
752
- * Numbers with `exponent >= PrecisionCutoff` are always considered integers.
753
- */
754
- isInteger(): boolean;
755
636
  /**
756
637
  * Allows implicit coercion via `+an(1500)` (returns `toNumber()`) and
757
638
  * template literals / string concatenation (returns `toString()`).
758
- *
759
- * `hint === "number"` → `toNumber()`; all other hints → `toString()`.
760
639
  */
761
640
  [Symbol.toPrimitive](hint: string): number | string;
762
641
  /**
763
642
  * Formats this number as a string using the given notation plugin.
764
643
  *
765
- * Defaults to {@link scientificNotation} when no plugin is provided.
766
- * `decimals` controls the number of decimal places passed to the plugin and defaults to `2`.
767
- *
768
- * @example
769
- * new ArbitraryNumber(1.5, 3).toString(); // "1.50e+3"
770
- * new ArbitraryNumber(1.5, 3).toString(unitNotation); // "1.50 K"
771
- * new ArbitraryNumber(1.5, 3).toString(unitNotation, 4); // "1.5000 K"
772
- *
773
- * @param notation - The formatting plugin to use.
774
- * @param decimals - Number of decimal places to render. Defaults to `2`.
644
+ * Defaults to `scientificNotation` when no plugin is provided.
645
+ * `decimals` controls decimal places and defaults to `defaults.notationDecimals`.
775
646
  */
776
647
  toString(notation?: NotationPlugin, decimals?: number): string;
777
648
  }
649
+ /**
650
+ * An immutable wrapper around `ArbitraryNumber`.
651
+ *
652
+ * Created via `number.freeze()`. All mutating methods throw `ArbitraryNumberMutationError`.
653
+ * Call `.clone()` to get a fresh, mutable `ArbitraryNumber`.
654
+ *
655
+ * @example
656
+ * const frozen = gold.freeze();
657
+ * frozen.add(drop); // throws ArbitraryNumberMutationError
658
+ * const mutable = frozen.clone(); // fresh mutable copy
659
+ */
660
+ declare class FrozenArbitraryNumber extends ArbitraryNumber {
661
+ /** @internal */
662
+ constructor(coefficient: number, exponent: number);
663
+ private _throwMutation;
664
+ add(_other: ArbitraryNumber): never;
665
+ sub(_other: ArbitraryNumber): never;
666
+ mul(_other: ArbitraryNumber): never;
667
+ div(_other: ArbitraryNumber): never;
668
+ negate(): never;
669
+ abs(): never;
670
+ pow(_n: number): never;
671
+ mulAdd(_m: ArbitraryNumber, _a: ArbitraryNumber): never;
672
+ addMul(_a: ArbitraryNumber, _m: ArbitraryNumber): never;
673
+ mulSub(_m: ArbitraryNumber, _s: ArbitraryNumber): never;
674
+ subMul(_s: ArbitraryNumber, _m: ArbitraryNumber): never;
675
+ divAdd(_d: ArbitraryNumber, _a: ArbitraryNumber): never;
676
+ mulDiv(_m: ArbitraryNumber, _d: ArbitraryNumber): never;
677
+ floor(): never;
678
+ ceil(): never;
679
+ round(): never;
680
+ trunc(): never;
681
+ sqrt(): never;
682
+ cbrt(): never;
683
+ }
778
684
 
779
685
  /**
780
686
  * Base class for all errors thrown by the arbitrary-numbers library.
@@ -818,111 +724,38 @@ declare class ArbitraryNumberDomainError extends ArbitraryNumberError {
818
724
  readonly context: Record<string, number>;
819
725
  constructor(message: string, context: Record<string, number>);
820
726
  }
821
-
822
- declare const an: AnFunction;
823
-
824
727
  /**
825
- * Fluent builder for multi-step `ArbitraryNumber` expressions.
826
- *
827
- * Each method mutates the accumulated value in-place and returns `this`,
828
- * enabling a readable left-to-right pipeline with no expression-tree
829
- * overhead. Every step delegates directly to the underlying
830
- * `ArbitraryNumber` method - fused variants (`mulAdd`, `mulSub`, etc.)
831
- * are available here too.
728
+ * Thrown when a mutating method is called on a frozen `ArbitraryNumber`.
832
729
  *
833
730
  * @example
834
- * // Damage formula: ((base - armour) * mult) + flatBonus
835
- * const result = chain(base)
836
- * .subMul(armour, multiplier)
837
- * .add(flatBonus)
838
- * .done();
839
- *
840
- * @remarks
841
- * No deferred execution - each call runs immediately. Overhead vs direct
842
- * method chaining is a single extra method call + `return this` per step
843
- * (~1-2 ns). Use fused ops for hot inner loops; the builder is optimised
844
- * for readability in complex multi-step formulas.
731
+ * try {
732
+ * frozen.add(other);
733
+ * } catch (e) {
734
+ * if (e instanceof ArbitraryNumberMutationError) { ... }
735
+ * }
845
736
  */
846
- declare class AnChain {
847
- private value;
848
- private constructor();
849
- /** Creates an `AnChain` from an `ArbitraryNumber` or a plain `number`. */
850
- static from(value: ArbitraryNumber | number): AnChain;
851
- /** Adds `other` to the accumulated value. */
852
- add(other: ArbitraryNumber): this;
853
- /** Subtracts `other` from the accumulated value. */
854
- sub(other: ArbitraryNumber): this;
855
- /** Multiplies the accumulated value by `other`. */
856
- mul(other: ArbitraryNumber): this;
857
- /** Divides the accumulated value by `other`. */
858
- div(other: ArbitraryNumber): this;
859
- /** Raises the accumulated value to `exp`. */
860
- pow(exp: number): this;
861
- /** `(this * mult) + add` */
862
- mulAdd(mult: ArbitraryNumber, add: ArbitraryNumber): this;
863
- /** `(this + add) * mult` */
864
- addMul(add: ArbitraryNumber, mult: ArbitraryNumber): this;
865
- /** `(this * mult) - sub` */
866
- mulSub(mult: ArbitraryNumber, sub: ArbitraryNumber): this;
867
- /** `(this - sub) * mult` */
868
- subMul(sub: ArbitraryNumber, mult: ArbitraryNumber): this;
869
- /** `(this / div) + add` */
870
- divAdd(div: ArbitraryNumber, add: ArbitraryNumber): this;
871
- /** Absolute value. */
872
- abs(): this;
873
- /** Negates the accumulated value. */
874
- neg(): this;
875
- /** Square root of the accumulated value. */
876
- sqrt(): this;
877
- /** Rounds down to the nearest integer. */
878
- floor(): this;
879
- /** Rounds up to the nearest integer. */
880
- ceil(): this;
881
- /** Rounds to the nearest integer. */
882
- round(): this;
883
- /** Returns the accumulated `ArbitraryNumber` result. */
884
- done(): ArbitraryNumber;
737
+ declare class ArbitraryNumberMutationError extends ArbitraryNumberDomainError {
738
+ constructor(message: string);
885
739
  }
886
- /**
887
- * Creates an {@link AnChain} builder starting from `value`.
888
- *
889
- * Mirrors the `an` factory shorthand.
890
- *
891
- * @example
892
- * import { chain, an } from 'arbitrary-numbers';
893
- *
894
- * const result = chain(an(1.5, 6))
895
- * .mulAdd(multiplier, bonus)
896
- * .floor()
897
- * .done();
898
- */
899
- declare function chain(value: ArbitraryNumber | number): AnChain;
900
740
 
901
- type FormulaStep = (value: ArbitraryNumber) => ArbitraryNumber;
741
+ declare const an: AnFunction;
742
+
743
+ type FormulaStep = (value: ArbitraryNumber) => void;
902
744
  /**
903
745
  * A reusable, named pipeline of `ArbitraryNumber` operations.
904
746
  *
905
- * Unlike {@link AnChain}, which executes each step immediately against an
906
- * accumulated value, `AnFormula` stores the operations as a list of closures
907
- * and runs them only when {@link apply} is called. The same formula can be
908
- * applied to any number of values without re-defining the pipeline.
909
- *
910
- * Each builder method returns a **new** `AnFormula` - the original is
911
- * unchanged. This makes branching and composition safe:
747
+ * `AnFormula` stores operations as closures and runs them on `.apply()` or `.applyInPlace()`.
748
+ * The same formula can be applied to any number of values without re-defining the pipeline.
912
749
  *
913
- * @example
914
- * const base = formula().mul(an(2));
915
- * const withFloor = base.floor(); // new formula - base is unchanged
916
- * const withCeil = base.ceil(); // another branch from base
750
+ * Each builder method returns a **new** `AnFormula` — the original is unchanged.
917
751
  *
918
752
  * @example
919
- * // Define once, apply to many values
920
753
  * const armorReduction = formula("Armor Reduction")
921
754
  * .subMul(armor, an(0.75))
922
755
  * .floor();
923
756
  *
924
- * const physDamage = armorReduction.apply(physBase);
925
- * const magDamage = armorReduction.apply(magBase);
757
+ * const physDamage = armorReduction.apply(physBase); // physBase unchanged
758
+ * armorReduction.applyInPlace(enemyAtk); // enemyAtk mutated
926
759
  *
927
760
  * @example
928
761
  * // Compose formulas
@@ -933,21 +766,12 @@ type FormulaStep = (value: ArbitraryNumber) => ArbitraryNumber;
933
766
  declare class AnFormula {
934
767
  private readonly _name;
935
768
  private readonly steps;
936
- /**
937
- * Prefer the {@link formula} factory function over calling this directly.
938
- */
769
+ /** Prefer the {@link formula} factory function over calling this directly. */
939
770
  constructor(name?: Maybe<string>, steps?: ReadonlyArray<FormulaStep>);
940
771
  /** The name passed to {@link formula}, if any. */
941
772
  get name(): Maybe<string>;
942
773
  /**
943
774
  * Returns a copy of this formula with a new name, leaving the original unchanged.
944
- *
945
- * @param name - The new name.
946
- * @example
947
- * const base = formula().mul(an(2));
948
- * const named = base.named("Double");
949
- * named.name // "Double"
950
- * base.name // undefined
951
775
  */
952
776
  named(name: string): AnFormula;
953
777
  private step;
@@ -973,7 +797,7 @@ declare class AnFormula {
973
797
  divAdd(div: ArbitraryNumber, add: ArbitraryNumber): AnFormula;
974
798
  /** Appends `abs()` to the pipeline. */
975
799
  abs(): AnFormula;
976
- /** Appends `neg()` to the pipeline. */
800
+ /** Appends `negate()` to the pipeline. */
977
801
  neg(): AnFormula;
978
802
  /** Appends `sqrt()` to the pipeline. */
979
803
  sqrt(): AnFormula;
@@ -987,50 +811,42 @@ declare class AnFormula {
987
811
  * Returns a new formula that first applies `this`, then applies `next`.
988
812
  *
989
813
  * Neither operand is mutated.
990
- *
991
- * @param next - The formula to apply after `this`.
992
- * @example
993
- * const full = armorReduction.then(critBonus);
994
- * const result = full.apply(baseDamage);
995
814
  */
996
815
  then(next: AnFormula): AnFormula;
997
816
  /**
998
- * Runs this formula's pipeline against `value` and returns the result.
817
+ * Clones `value` once, runs the pipeline against the clone, and returns it.
999
818
  *
1000
- * The formula itself is unchanged - call `apply` as many times as needed.
819
+ * The original `value` is never mutated.
1001
820
  *
1002
- * @param value - The starting value. Plain `number` is coerced via `ArbitraryNumber.from`.
1003
- * @throws `"ArbitraryNumber.from: value must be finite"` when a plain `number` is non-finite.
1004
821
  * @example
1005
- * const damage = damageFormula.apply(baseDamage);
1006
- * const scaled = damageFormula.apply(boostedBase);
822
+ * const damage = damageFormula.apply(playerAtk); // playerAtk unchanged
1007
823
  */
1008
824
  apply(value: ArbitraryNumber | number): ArbitraryNumber;
825
+ /**
826
+ * Runs the pipeline directly against `value`, mutating it in-place.
827
+ *
828
+ * Use on hot paths where you don't need to preserve the original value.
829
+ *
830
+ * @example
831
+ * damageFormula.applyInPlace(enemyAtk); // enemyAtk is mutated
832
+ */
833
+ applyInPlace(value: ArbitraryNumber): void;
1009
834
  }
1010
835
  /**
1011
836
  * Creates an {@link AnFormula} pipeline, optionally named.
1012
837
  *
1013
- * Build the pipeline by chaining methods - each returns a new `AnFormula`
1014
- * so the original is always safe to branch or reuse. Call {@link AnFormula.apply}
1015
- * to run the pipeline against a value.
838
+ * Build the pipeline by chaining methods each returns a new `AnFormula`.
839
+ * Call {@link AnFormula.apply} or {@link AnFormula.applyInPlace} to run it.
1016
840
  *
1017
- * @param name - Optional label, available via {@link AnFormula.name} for debugging.
1018
841
  * @example
1019
842
  * import { formula, an } from 'arbitrary-numbers';
1020
843
  *
1021
844
  * const armorReduction = formula("Armor Reduction")
1022
- * .subMul(armor, an(0.75)) // (base - armor) * 0.75
845
+ * .subMul(armor, an(0.75))
1023
846
  * .floor();
1024
847
  *
1025
- * const critBonus = formula("Crit Bonus").mul(critMult).ceil();
1026
- *
1027
- * // Reuse across many values
1028
848
  * const physDmg = armorReduction.apply(physBase);
1029
- * const magDmg = armorReduction.apply(magBase);
1030
- *
1031
- * // Compose
1032
- * const full = armorReduction.then(critBonus);
1033
- * const result = full.apply(baseDamage);
849
+ * armorReduction.applyInPlace(tempValue);
1034
850
  */
1035
851
  declare function formula(name?: Maybe<string>): AnFormula;
1036
852
 
@@ -1276,18 +1092,15 @@ declare class ArbitraryNumberGuard {
1276
1092
  */
1277
1093
  static isArbitraryNumber(obj: unknown): obj is ArbitraryNumber;
1278
1094
  /**
1279
- * Returns `true` if `obj` has the shape of a {@link NormalizedNumber}
1280
- * (i.e. has numeric `coefficient` and `exponent` properties).
1095
+ * Returns `true` if `obj` has the shape `{ coefficient: number; exponent: number }`.
1281
1096
  *
1282
- * Note: both `ArbitraryNumber` instances and plain objects with the right
1283
- * shape will pass this check. Use {@link isArbitraryNumber} when you need
1284
- * to distinguish between the two.
1285
- *
1286
- * @param obj - The value to test.
1287
- * @returns `true` when `obj` has `typeof coefficient === "number"` and
1288
- * `typeof exponent === "number"`.
1097
+ * Both `ArbitraryNumber` instances and plain objects with the right shape pass this
1098
+ * check. Use {@link isArbitraryNumber} to distinguish the two.
1289
1099
  */
1290
- static isNormalizedNumber(obj: unknown): obj is NormalizedNumber;
1100
+ static isNormalizedNumber(obj: unknown): obj is {
1101
+ coefficient: number;
1102
+ exponent: number;
1103
+ };
1291
1104
  /**
1292
1105
  * Returns `true` if `obj` is an {@link ArbitraryNumber} with a value of zero.
1293
1106
  *
@@ -1297,114 +1110,10 @@ declare class ArbitraryNumberGuard {
1297
1110
  static isZero(obj: unknown): boolean;
1298
1111
  }
1299
1112
 
1300
- /**
1301
- * Convenience helpers for mixed `number | ArbitraryNumber` inputs.
1302
- *
1303
- * Each method accepts either type and coerces plain `number` values via
1304
- * {@link ArbitraryNumber.from} before delegating to the corresponding instance method.
1305
- *
1306
- * Prefer `ArbitraryNumber` instance methods directly on hot paths - this class is
1307
- * intended for system boundaries (event handlers, serialisation, UI callbacks) where
1308
- * the input type is unknown.
1309
- *
1310
- * @example
1311
- * import { ArbitraryNumberOps as ops } from "arbitrary-numbers";
1312
- * ops.add(1500, 2500) // ArbitraryNumber (4000)
1313
- * ops.mul(an(2, 0), 5) // ArbitraryNumber (10)
1314
- * ops.from(1_500_000) // ArbitraryNumber { coefficient: 1.5, exponent: 6 }
1315
- */
1316
- declare class ArbitraryNumberOps {
1317
- /**
1318
- * Converts `value` to an `ArbitraryNumber`, returning it unchanged if it already is one.
1319
- *
1320
- * @param value - A plain `number` or an existing `ArbitraryNumber`.
1321
- * @returns The corresponding `ArbitraryNumber`.
1322
- * @throws `ArbitraryNumberInputError` when `value` is `NaN`, `Infinity`, or `-Infinity`.
1323
- */
1324
- static from(value: ArbitraryNumberish): ArbitraryNumber;
1325
- /**
1326
- * Converts `value` to an `ArbitraryNumber`, returning `null` for non-finite inputs
1327
- * instead of throwing.
1328
- *
1329
- * Use this at system boundaries (form inputs, external APIs) where you want to handle
1330
- * bad input gracefully rather than catching an exception.
1331
- *
1332
- * @param value - A plain `number` or an existing `ArbitraryNumber`.
1333
- * @returns The `ArbitraryNumber`, or `null` if the input is `NaN`, `Infinity`, or `-Infinity`.
1334
- *
1335
- * @example
1336
- * ops.tryFrom(1500) // ArbitraryNumber { coefficient: 1.5, exponent: 3 }
1337
- * ops.tryFrom(Infinity) // null
1338
- * ops.tryFrom(NaN) // null
1339
- */
1340
- static tryFrom(value: ArbitraryNumberish): Nullable<ArbitraryNumber>;
1341
- /**
1342
- * Returns `left + right`, coercing both operands as needed.
1343
- *
1344
- * @param left - The augend.
1345
- * @param right - The addend.
1346
- * @example
1347
- * ops.add(1500, 2500) // ArbitraryNumber (4000)
1348
- */
1349
- static add(left: ArbitraryNumberish, right: ArbitraryNumberish): ArbitraryNumber;
1350
- /**
1351
- * Returns `left - right`, coercing both operands as needed.
1352
- *
1353
- * @param left - The minuend.
1354
- * @param right - The subtrahend.
1355
- * @example
1356
- * ops.sub(5000, 1500) // ArbitraryNumber (3500)
1357
- */
1358
- static sub(left: ArbitraryNumberish, right: ArbitraryNumberish): ArbitraryNumber;
1359
- /**
1360
- * Returns `left * right`, coercing both operands as needed.
1361
- *
1362
- * @param left - The multiplicand.
1363
- * @param right - The multiplier.
1364
- * @example
1365
- * ops.mul(an(1, 3), 5) // ArbitraryNumber (5000)
1366
- */
1367
- static mul(left: ArbitraryNumberish, right: ArbitraryNumberish): ArbitraryNumber;
1368
- /**
1369
- * Returns `left / right`, coercing both operands as needed.
1370
- *
1371
- * @param left - The dividend.
1372
- * @param right - The divisor.
1373
- * @throws `"Division by zero"` when `right` is zero.
1374
- * @example
1375
- * ops.div(an(1, 6), 1000) // ArbitraryNumber (1000)
1376
- */
1377
- static div(left: ArbitraryNumberish, right: ArbitraryNumberish): ArbitraryNumber;
1378
- /**
1379
- * Compares `left` to `right`.
1380
- *
1381
- * @param left - The left operand.
1382
- * @param right - The right operand.
1383
- * @returns `1` if `left > right`, `-1` if `left < right`, `0` if equal.
1384
- * @example
1385
- * ops.compare(5000, 1500) // 1
1386
- */
1387
- static compare(left: ArbitraryNumberish, right: ArbitraryNumberish): number;
1388
- /**
1389
- * Clamps `value` to the inclusive range `[min, max]`, coercing all inputs as needed.
1390
- *
1391
- * @param value - The value to clamp.
1392
- * @param min - The lower bound (inclusive).
1393
- * @param max - The upper bound (inclusive).
1394
- * @example
1395
- * ops.clamp(500, 1000, 2000) // ArbitraryNumber (1000) - below min, returns min
1396
- */
1397
- static clamp(value: ArbitraryNumberish, min: ArbitraryNumberish, max: ArbitraryNumberish): ArbitraryNumber;
1398
- }
1399
-
1400
1113
  /**
1401
1114
  * Domain-level helpers for common game and simulation patterns.
1402
1115
  *
1403
- * These sit above the core arithmetic layer - each method accepts
1404
- * mixed input (`number | ArbitraryNumber`) via
1405
- * {@link ArbitraryNumberOps.from} so they work at system boundaries
1406
- * where you may receive raw numbers.
1407
- *
1116
+ * Accepts mixed input (`number | ArbitraryNumber`) via `ArbitraryNumber.from`.
1408
1117
  * For hot-path code, use `ArbitraryNumber` methods directly.
1409
1118
  */
1410
1119
  declare class ArbitraryNumberHelpers {
@@ -1412,9 +1121,6 @@ declare class ArbitraryNumberHelpers {
1412
1121
  /**
1413
1122
  * Returns `true` when `value >= threshold`.
1414
1123
  *
1415
- * @param value - The value to test.
1416
- * @param threshold - The minimum required value.
1417
- * @returns `true` when `value >= threshold`.
1418
1124
  * @example
1419
1125
  * ArbitraryNumberHelpers.meetsOrExceeds(gold, upgradeCost)
1420
1126
  */
@@ -1424,9 +1130,6 @@ declare class ArbitraryNumberHelpers {
1424
1130
  *
1425
1131
  * Equivalent to `floor(total / step)`.
1426
1132
  *
1427
- * @param total - The total available amount.
1428
- * @param step - The cost or size of one unit. Must be greater than zero.
1429
- * @returns The number of whole units that fit, as an `ArbitraryNumber`.
1430
1133
  * @throws `"step must be greater than zero"` when `step <= 0`.
1431
1134
  * @example
1432
1135
  * const canBuy = ArbitraryNumberHelpers.wholeMultipleCount(gold, upgradeCost);
@@ -1435,14 +1138,10 @@ declare class ArbitraryNumberHelpers {
1435
1138
  /**
1436
1139
  * Returns `value - delta`, clamped to a minimum of `floor` (default `0`).
1437
1140
  *
1438
- * @param value - The starting value.
1439
- * @param delta - The amount to subtract.
1440
- * @param floor - The minimum result. Defaults to `ArbitraryNumber.Zero`.
1441
- * @returns `max(value - delta, floor)`.
1442
1141
  * @example
1443
1142
  * health = ArbitraryNumberHelpers.subtractWithFloor(health, damage);
1444
1143
  */
1445
1144
  static subtractWithFloor(value: ArbitraryNumberish, delta: ArbitraryNumberish, floor?: ArbitraryNumberish): ArbitraryNumber;
1446
1145
  }
1447
1146
 
1448
- export { AlphabetNotation, type AlphabetNotationOptions, AnChain, AnFormula, type AnFunction, ArbitraryNumber, ArbitraryNumberDomainError, ArbitraryNumberError, ArbitraryNumberGuard, ArbitraryNumberHelpers, ArbitraryNumberInputError, type ArbitraryNumberJson, ArbitraryNumberOps, type ArbitraryNumberish, CLASSIC_UNITS, COMPACT_UNITS, type Maybe, type Mod3, type NormalizedNumber, type NotationPlugin, type Nullable, ScientificNotation, type Signum, SuffixNotationBase, type SuffixNotationPlugin, type SuffixNotationPluginOptions, type SuffixProvider, type Unit, type UnitArray, UnitNotation, type UnitNotationOptions, alphabetSuffix, an, chain, formula, ArbitraryNumberGuard as guard, ArbitraryNumberHelpers as helpers, letterNotation, ArbitraryNumberOps as ops, scientificNotation, unitNotation };
1147
+ export { AlphabetNotation, type AlphabetNotationOptions, AnFormula, type AnFunction, ArbitraryNumber, type ArbitraryNumberDefaults, ArbitraryNumberDomainError, ArbitraryNumberError, ArbitraryNumberGuard, ArbitraryNumberHelpers, ArbitraryNumberInputError, type ArbitraryNumberJson, ArbitraryNumberMutationError, type ArbitraryNumberish, CLASSIC_UNITS, COMPACT_UNITS, FrozenArbitraryNumber, type Maybe, type Mod3, type NotationPlugin, type Nullable, ScientificNotation, type Signum, SuffixNotationBase, type SuffixNotationPlugin, type SuffixNotationPluginOptions, type SuffixProvider, type Unit, type UnitArray, UnitNotation, type UnitNotationOptions, alphabetSuffix, an, formula, ArbitraryNumberGuard as guard, ArbitraryNumberHelpers as helpers, letterNotation, scientificNotation, unitNotation };