std/random/_pcg32.ts

101 lines
3.3 KiB
TypeScript
Raw Normal View History

// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
// Based on Rust `rand` crate (https://github.com/rust-random/rand). Apache-2.0 + MIT license.
/** Multiplier for the PCG32 algorithm. */
const MUL: bigint = 6364136223846793005n;
/** Initial increment for the PCG32 algorithm. Only used during seeding. */
const INC: bigint = 11634580027462260723n;
// Constants are for 64-bit state, 32-bit output
const ROTATE = 59n; // 64 - 5
const XSHIFT = 18n; // (5 + 32) / 2
const SPARE = 27n; // 64 - 32 - 5
/**
* Internal state for the PCG32 algorithm.
* `state` prop is mutated by each step, whereas `inc` prop remains constant.
*/
type PcgMutableState = {
state: bigint;
inc: bigint;
};
/**
* Modified from https://github.com/rust-random/rand/blob/f7bbccaedf6c63b02855b90b003c9b1a4d1fd1cb/rand_pcg/src/pcg64.rs#L129-L135
*/
export function fromSeed(seed: Uint8Array) {
const d = new DataView(seed.buffer);
return fromStateIncr(d.getBigUint64(0, true), d.getBigUint64(8, true) | 1n);
}
/**
* Mutates `pcg` by advancing `pcg.state`.
*/
function step(pgc: PcgMutableState) {
pgc.state = BigInt.asUintN(64, pgc.state * MUL + (pgc.inc | 1n));
}
/**
* Modified from https://github.com/rust-random/rand/blob/f7bbccaedf6c63b02855b90b003c9b1a4d1fd1cb/rand_pcg/src/pcg64.rs#L99-L105
*/
function fromStateIncr(state: bigint, inc: bigint): PcgMutableState {
const pcg: PcgMutableState = { state, inc };
// Move away from initial value
pcg.state = BigInt.asUintN(64, state + inc);
step(pcg);
return pcg;
}
/**
* Internal PCG32 implementation, used by both the public seeded random
* function and the seed generation algorithm.
*
* Modified from https://github.com/rust-random/rand/blob/f7bbccaedf6c63b02855b90b003c9b1a4d1fd1cb/rand_pcg/src/pcg64.rs#L140-L153
*
* `pcg.state` is internally advanced by the function.
*
* @param pcg The state and increment values to use for the PCG32 algorithm.
* @returns The next pseudo-random 32-bit integer.
*/
export function nextU32(pcg: PcgMutableState): number {
const state = pcg.state;
step(pcg);
// Output function XSH RR: xorshift high (bits), followed by a random rotate
const rot = state >> ROTATE;
const xsh = BigInt.asUintN(32, (state >> XSHIFT ^ state) >> SPARE);
return Number(rotateRightU32(xsh, rot));
}
// `n`, `rot`, and return val are all u32
function rotateRightU32(n: bigint, rot: bigint): bigint {
const left = BigInt.asUintN(32, n << (-rot & 31n));
const right = n >> rot;
return left | right;
}
/**
* Convert a scalar bigint seed to a Uint8Array of the specified length.
* Modified from https://github.com/rust-random/rand/blob/f7bbccaedf6c63b02855b90b003c9b1a4d1fd1cb/rand_core/src/lib.rs#L359-L388
*/
export function seedFromU64(state: bigint, numBytes: number): Uint8Array {
const seed = new Uint8Array(numBytes);
const pgc: PcgMutableState = { state: BigInt.asUintN(64, state), inc: INC };
// We advance the state first (to get away from the input value,
// in case it has low Hamming Weight).
step(pgc);
for (let i = 0; i < Math.floor(numBytes / 4); ++i) {
new DataView(seed.buffer).setUint32(i * 4, nextU32(pgc), true);
}
const rem = numBytes % 4;
if (rem) {
const bytes = new Uint8Array(4);
new DataView(bytes.buffer).setUint32(0, nextU32(pgc), true);
seed.set(bytes.subarray(0, rem), numBytes - rem);
}
return seed;
}