@spissvinkel/alea
Version:
Seedable PRNG for JS/TS
124 lines • 4.26 kB
JavaScript
/**
* Initialize a new PRNG
*
* @param seed an optional seed value
* @returns an initialized PRNG
*/
export const mkAlea = (seed = defaultSeed()) => restoreAlea(mkState(seed));
/**
* Initialize a new PRNG from a previously saved state, effectively allowing the previous PRNG to be resumed
*
* @param state a state object, probably retrieved from an existing PRNG with [[`AleaPRNG.getState`]]
* @returns an initialized PRNG
*/
export const restoreAlea = (state) => ({
random: () => random(state),
uint32: () => uint32(state),
fract53: () => fract53(state),
nextT: f => {
const g = nextT(f);
return () => g(state);
},
getState: () => (Object.assign({}, state))
});
/**
* @param state a state object, probably created with [[`mkState`]]. Will be updated as a side effect
* @returns a pseudo-random floating-point number in the interval `[0, 1)` (like `Math.random()`)
*/
export const random = (state) => {
let t = 2091639 * state.s0 + state.c * 2.3283064365386963e-10; // 2^-32
state.s0 = state.s1;
state.s1 = state.s2;
return state.s2 = t - (state.c = t | 0);
};
/**
* @param state a state object, probably generated by [[`mkState`]]. Will be updated as a side effect
* @returns a pseudo-random unsigned 32-bit integer in the interval `[0, 2^32)`
*/
export const uint32 = (state) => random(state) * 0x100000000; // 2^32
/**
* @param state a state object, probably generated with [[`mkState`]]. Will be updated as a side effect
* @returns a pseudo-random full 53-bit fraction in the interval `[0, 1)`. (Slower than [[`random`]] but higher precision)
*/
export const fract53 = (state) => random(state) + (random(state) * 0x200000 | 0) * 1.1102230246251565e-16; // 2^-53
/**
* Creates utility functions based on the [[`random`]] function, e.g.:
* ```
* const state: AleaState = mkState('123');
* const nextBool: (state: AleaState) => boolean = nextT(n => n < 0.5);
* // calling `nextBool(state)` now returns `true` or `false` at random
* ```
* @param f a function to transform a number in the interval `[0, 1)` to a value of type `T`
* @param state a state object, probably generated with [[`mkState`]]. Will be updated as a side effect
* @returns a function that, when invoked with a state, transforms the next pseudo-random number `n` to a `T` using the provided function `f`
*/
export const nextT = (f) => (state) => f(random(state));
/**
* Initializes a new PRNG state object using the provided seed value
*
* @param seed a seed value
* @returns a new state object based on the provided `seed`
*/
export const mkState = (seed) => initState(seed, emptyState());
/**
* Initializes a recycled PRNG state object using the provided seed value
*
* @param seed a seed value
* @param state a state object to initialize
* @returns the provided `state` object initialized using the provided `seed`
*/
export const initState = (seed, state) => {
let n = INITIAL_MASH_N;
let s0 = mashResult(n = mash(' ', n));
let s1 = mashResult(n = mash(' ', n));
let s2 = mashResult(n = mash(' ', n));
let c = 1;
s0 -= mashResult(n = mash(seed, n));
if (s0 < 0)
s0 += 1;
s1 -= mashResult(n = mash(seed, n));
if (s1 < 0)
s1 += 1;
s2 -= mashResult(n = mash(seed, n));
if (s2 < 0)
s2 += 1;
state.s0 = s0;
state.s1 = s1;
state.s2 = s2;
state.c = c;
return state;
};
/**
* Creates an uninitialized PRNG state object.
* Must be initialized with [[`initState`]] before use
*
* @returns a new, uninitialized state object
*/
export const emptyState = () => {
const s0 = 0;
const s1 = 0;
const s2 = 0;
const c = 0;
return { s0, s1, s2, c };
};
/** @internal */
const INITIAL_MASH_N = 0xefc8249d;
/** @internal */
const mash = (data, n) => {
for (let i = 0; i < data.length; i++) {
n += data.charCodeAt(i);
let h = 0.02519603282416938 * n;
n = h >>> 0;
h -= n;
h *= n;
n = h >>> 0;
h -= n;
n += h * 0x100000000; // 2^32
}
return n;
};
/** @internal */
const mashResult = (n) => (n >>> 0) * 2.3283064365386963e-10; // 2^-32
/** @internal */
const defaultSeed = () => `${Date.now()}`;
//# sourceMappingURL=index.js.map