Skip to content

Instantly share code, notes, and snippets.

@SalvatorePreviti
Created September 18, 2023 09:38
Show Gist options
  • Save SalvatorePreviti/55cd69800ef2e4be9135324c1bc899f1 to your computer and use it in GitHub Desktop.
Save SalvatorePreviti/55cd69800ef2e4be9135324c1bc899f1 to your computer and use it in GitHub Desktop.
describe('newPRNG', () => {
test('should generate seeded random values', () => {
expect(newPRNG(123)(new Array(5))).toEqual([-220828386, -1556571610, 1450074142, -2011800852, 1359997245]);
expect(Buffer.from(newPRNG(123)(new Int8Array(20))).toString('hex')).toBe(
'1ef2d66d26a3389a1e566e60ec88165a3d510fe9',
);
expect(Buffer.from(newPRNG(123)(new Int8Array(20))).toString('hex')).toBe(
'1ef2d66d26a3389a1e566e60ec88165a3d510fe9',
);
expect(Buffer.from(newPRNG(123)(new Uint8ClampedArray(10))).toString('hex')).toBe('1ef2d66d26a3389a1e56');
expect(Buffer.from(newPRNG(123)(new Int16Array(10))).toString('hex')).toBe(
'1ef2d66d26a3389a1e566e60ec88165a3d510fe9',
);
expect(Buffer.from(newPRNG(123)(new Uint16Array(10))).toString('hex')).toBe(
'1ef2d66d26a3389a1e566e60ec88165a3d510fe9',
);
expect(Buffer.from(newPRNG(123)(new Uint32Array(5))).toString('hex')).toBe(
'1ef2d66d26a3389a1e566e60ec88165a3d510fe9',
);
expect(Buffer.from(newPRNG(123)(new Int32Array(5))).toString('hex')).toBe(
'1ef2d66d26a3389a1e566e60ec88165a3d510fe9',
);
expect(Buffer.from(newPRNG(123)(new BigInt64Array(3))).toString('hex')).toBe(
'1ef2d66d26a3389a1e566e60ec88165a3d510fe945489fa0',
);
expect(Buffer.from(newPRNG(123)(new BigUint64Array(3))).toString('hex')).toBe(
'1ef2d66d26a3389a1e566e60ec88165a3d510fe945489fa0',
);
expect(Buffer.from(newPRNG(123)(new DataView(new ArrayBuffer(20))).buffer).toString('hex')).toBe(
'1ef2d66d26a3389a1e566e60ec88165a3d510fe9',
);
});
test('should call randomizer if provided', () => {
let x = 0;
expect(Buffer.from(newPRNG(1, () => ++x)(new Uint8Array(200))).toString('hex')).toBe(
'db12adaf757fe47e954775b89bd6afd5be384f6981f9929c463a143e7dc208ddbf2e660be05c67303854fe1215e68c469b45f13a11ca31' +
'da7ef1c8f9930bd7ef983605d0c72afcd33bdcec65b9bab1204f571141e33a296167428f0cf80d76d787987e608e541f9d287da3d021d' +
'908a9be8bbb75d4c1e8fb195f5c7ac7379375ee31bfa3d2ae6867c288c59fd2205ab7d71768fbc1b43a1b00b28e30f71cf0be140b6a10' +
'827ce3d012296aaea8100089794abb717a940393fc03f0bcdf51cab7cbe4b45676e54c33',
);
expect(x).toBe(10);
});
});
/** A pseudo random number generator */
export interface PRNG {
/** Returns a pseudorandom 32 bit unsigned integer */
(): number;
/** Fills the given typed array with pseudorandom values */
<T extends IntegerTypedArray | DataView | ArrayBuffer | number[] | null>(typedArray?: T): T;
}
/**
* Returns a new pseudo random generator function that can be used to fill typed arrays with random values.
* Based on xoshiro algorithm, see http://prng.di.unimi.it/
* Extremely compact when minified, ~445 bytes.
* Useful for generating testing data or to obtain a repeatable sequence of pseudorandom numbers.
* @param seed The seed to use, a 53 bit integer
* @param randomizer An optional function that can returns a random integer to be mixed.
* @example
* const prng = newPRNG(123);
* const randomUint32: number = prng();
* const bytes: Uint8Array = prng(new Uint8Array(10));
* const random32bitInts: number[] = prng(new Array(12))
*/
export const newPRNG = /*@__PURE__*/ (seed: number, randomizer?: () => number): PRNG => {
let q!: number;
let n!: number;
let ab!: number; // accumulator for bytes
let an = 0; // number of bits available in acc
let state = 0x3a522fb3; // initialization and randomization
let s0 = 0x54e241; // xoshiro state 0
let s1 = state * 97; // xoshiro state 1
let s2 = s0 * 13; // xoshiro state 2
let s3 = seed; // xoshiro state 3
const prng = (out?: UnsafeAny) => {
if (out !== undefined) {
if (out) {
n = out.BYTES_PER_ELEMENT * 8;
if (!n || n > 4) {
n = out.byteLength;
out = n ? new Uint8Array(out.buffer || out, out.byteOffset | 0, n) : out;
n = n ? 8 : 32;
}
for (let i = 0, m = -1 >>> (32 - n); i < out.length; ++i) {
if (an < n) {
an = 32;
ab = prng();
}
out[i] = m & (ab >>> an);
an -= n;
}
}
return out;
}
if (state > 23) {
state >>= 2;
seed = prng() - seed / 7;
s1 ^= (seed * 17) ^ (state & 1 && randomizer! && randomizer());
}
if (!--state) {
state = 16 ^ (s2 & 7);
seed ^= state ^ (randomizer! && randomizer());
}
q = s1;
s2 ^= s0;
s3 ^= s1;
s1 ^= s2;
s0 ^= s3;
s2 ^= q << 9;
s3 = (s3 << 11) ^ (s3 >> 21);
q *= 5;
return (seed ^ (((q << 7) ^ (q >> 25)) * 9)) >>> 0;
};
return prng;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment