Luau Math.random & randomseed accurately ported to JavaScript

I made this for a different project I am working on right now, and I thought I would share it with you guys because why not

const
	INT_MAX = Math.pow(2, 31) - 1,
	INT_MIN = -Math.pow(2, 31);
const clamp = (x, min, max) => Math.min(Math.max(x, min), max);
const toInt32 = x => Math.trunc(clamp(Number(x), INT_MIN, INT_MAX));

export default class PCG32 {
	MULT = 6364136223846793005n;
	INC = 105n; 
	MASK64 = (1n << 64n) - 1n;
	MASK32 = 0xFFFFFFFFn;
	state = 0n;

	#nextUint32() {
		const oldstate = this.state;

		this.state = (oldstate * this.MULT + (this.INC | 1n)) & this.MASK64;

		const xorshifted = Number(((((oldstate >> 18n) ^ oldstate) >> 27n) & this.MASK32));
		const rot = Number((oldstate >> 59n) & 31n);

		const rx = ((xorshifted >>> rot) | ((xorshifted << ((-rot) & 31)) >>> 0)) >>> 0;
		return rx >>> 0;
	}

	seed(value) {

		const seed = BigInt.asUintN(64, BigInt(toInt32(value)));

		this.state = 0n;
		this.#nextUint32(); 
		this.state = (this.state + seed) & this.MASK64;
		this.#nextUint32();
	}

	random(...args) {
		switch (args.length) {
			case 0: {
				const rl = this.#nextUint32();
				const rh = this.#nextUint32();

				const combined = (BigInt(rl) | (BigInt(rh) << 32n)) & this.MASK64;

				return Number(combined) / Number(1n << 64n);
			}

			case 1: {
				const u = toInt32(args[0]); 
				if (u < 1) throw new RangeError("interval is empty");
				const rnd = BigInt(this.#nextUint32());

				const prod = BigInt(u) * rnd;
				return Number((prod >> 32n) + 1n);
			}

			case 2: {
				const
					l = toInt32(args[0]),
					u = toInt32(args[1]);
				if (l > u) throw new RangeError("interval is empty");

				const ul = BigInt.asUintN(32, BigInt(u - l));
				if (ul === this.MASK32) throw new RangeError("interval is too large");

				const rnd = BigInt(this.#nextUint32());
				const prod = (ul + 1n) * rnd;
				const delta = prod >> 32n;
				const result = BigInt.asIntN(32, BigInt(l) + delta);
				return Number(result);
			}

			default:
				throw new TypeError("wrong number of arguments");
		}
	}
}

Feedback would be appreciated :pray: Thanks

man, I wish I could still read JavaScript, because then this would be more impressive!

1 Like