Initial Commit
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
import type { Rng } from "@suaveplan/types";
|
||||
import { pwgenError } from "../errors/codes.js";
|
||||
|
||||
/**
|
||||
* Bounded retry budget for the rejection loop. A correctly-implemented
|
||||
* `Rng.nextUint32` produces a uniformly-distributed uint32 every call; the
|
||||
* worst-case rejection probability is `(2^32 mod max) / 2^32 < max/2^32`,
|
||||
* which is < 0.0000019 for `max = 7776`. 1024 attempts give a worst-case
|
||||
* success probability of `1 - (max/2^32)^1024`, indistinguishable from 1.
|
||||
* Hitting this cap therefore proves the supplied `Rng` is broken.
|
||||
*/
|
||||
const REJECTION_RETRY_BUDGET = 1024;
|
||||
|
||||
/** Exclusive upper bound of the uint32 space. */
|
||||
const TWO_POW_32 = 0x1_0000_0000;
|
||||
|
||||
/**
|
||||
* Return a uniformly-distributed integer in the half-open interval
|
||||
* `[0, max)` using Lemire-style rejection sampling.
|
||||
*
|
||||
* Reference: Daniel Lemire, _Fast Random Integer Generation in an
|
||||
* Interval_, ACM Trans. Model. Comput. Simul. 2019,
|
||||
* https://arxiv.org/abs/1805.10941 §3 ("Nearly Divisionless").
|
||||
*
|
||||
* The naïve `Math.floor(rng() * max)` is biased for non-power-of-two
|
||||
* `max`. This implementation draws a uint32 and rejects values above the
|
||||
* threshold `2^32 - (2^32 mod max)`, re-drawing on rejection. The bias
|
||||
* disappears at the cost of an expected `< 2` draws per call.
|
||||
*
|
||||
* @throws PwgenError(PWGEN.RNG_FAILED) if `rng.nextUint32` returns
|
||||
* non-uint32 values for {@link REJECTION_RETRY_BUDGET} consecutive
|
||||
* attempts.
|
||||
*/
|
||||
export function randomInt(rng: Rng, max: number): number {
|
||||
if (!Number.isInteger(max) || max < 1 || max >= TWO_POW_32) {
|
||||
throw pwgenError(
|
||||
"PWGEN.RNG_FAILED",
|
||||
`randomInt requires max in [1, 2^32); received ${max}`
|
||||
);
|
||||
}
|
||||
const threshold = TWO_POW_32 - (TWO_POW_32 % max);
|
||||
for (let attempt = 0; attempt < REJECTION_RETRY_BUDGET; attempt++) {
|
||||
const draw = rng.nextUint32() >>> 0;
|
||||
if (draw < threshold) return draw % max;
|
||||
}
|
||||
throw pwgenError(
|
||||
"PWGEN.RNG_FAILED",
|
||||
`Rng failed to produce an in-range uint32 after ${REJECTION_RETRY_BUDGET} attempts`
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user