@informalsystems/quint
Version:
Core tool for the Quint specification language
132 lines • 5.13 kB
JavaScript
;
/*
* The interface to a pseudo random number generator. Currently, we are using
* the "squares" fast random number generator.
*
* See:
* Bernard Widynski. Squares: A Fast Counter-Based RNG. Arxiv, Mar 13, 2022.
*
* [1]: https://arxiv.org/pdf/2004.06278v7.pdf
*
* We have tried to use the package 'squares-rng', but it was too hard to make
* it run with NodeJS and VSCode, as 'squares-rng' is using the ESM modules.
*
* [squares-rng]: https://github.com/FlorisSteenkamp/squares-rng
*
* Hence, we have implemented the 'squares' function from the paper. Whereas
* the squares-rng package is using JS numbers and thus limits the number of
* produced integers to 2^32, we are using big integers and thus free to
* produce up to 2^64 integers, as in the original paper. Also, we are using a
* different key.
*
* Igor Konnov, Informal Systems, 2023.
*
* Copyright 2022-2023 Informal Systems
* Licensed under the Apache License, Version 2.0.
* See LICENSE in the project root for license information.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.newRng = void 0;
const assert_1 = require("assert");
// 2^32 as bigint
const U32 = 0x100000000n;
// 2^64 as bigint
const U64 = 0x10000000000000000n;
/**
* Create a new instance of Rng, given the supplied initial state.
* If no initial state is given, the generator is initialized from the current
* time and other system state.
*/
const newRng = (initialState) => {
// Produce a random integer with the system RNG.
// Since the system generator is using `number`,
// the number is in the range of [0, 2^53).
let state = initialState ?? BigInt(Math.floor(Math.random() * Number.MAX_SAFE_INTEGER));
return {
getState: () => {
return state;
},
setState: (s) => {
state = BigInt(s >= 0 ? s : -s) % U64;
},
next: (bound) => {
(0, assert_1.strict)(bound > 0n);
let input = bound;
let output = 0n;
let base = 1n;
while (input >= U32) {
// produce pseudo-random least significant 32 bits,
// while shifting the previous output to the left
output = output * U32 + squares64(state);
// advance the RNG state, while staying within 64 bits
state = (state + 1n) % U64;
// forget the least significant 32 bits of the input
input /= U32;
// shift the base by 32 bits to the left
base *= U32;
}
// Now we have to be careful to make `output` in the range [0, bound).
// Produce 32 bits. Since we are using the modulo operator here,
// the result is not exactly the uniform distribution.
// It may be biased towards some values, especially for
// the small values of `bound. If it becomes a problem in the future,
// we should figure out, how to make the distribution uniform.
output = (squares64(state) % input) * base + output;
// advance the RNG state, while staying within 64 bits
state = (state + 1n) % U64;
return output;
},
};
};
exports.newRng = newRng;
/**
* One of the keys used in this implementation. The key is mixed in the
* computation of the next pseudo-random number. It somehow affects the
* "randomness" of the produced values. For details, see the paper [1].
* This is the key number 4.
*
* See: https://squaresrng.wixsite.com/rand
*/
const key = 0xfb9e125878fa6cb3n;
/**
* Our TypeScript implementation of the below C code from the paper [1].
* We are not using the JS shift operators `<<` and `>>`, which go over
* conversions to 32-bit integers. Instead, we simply use `/` and `%` over
* bigint. One has to do measurements, to see what is more efficient.
* Since the reproducibility and the ability to restore the state is the key
* for us, and not the performance, this should be fine.
*
* This implementation produces up to 2^64 pseudo random unsigned
* 32-bit integers, in contrast to the squares-rng package, which
* produces up to 2^32 pseudo random unsigned 32-bit integers.
*
* The C code from the paper [1]:
*
* ```c
* inline static uint32_t squares32(uint64_t ctr, uint64_t key) {
* uint64_t x, y, z;
* y = x = ctr * key; z = y + key;
* x = x*x + y; x = (x>>32) | (x<<32); // round 1
* x = x*x + z; x = (x>>32) | (x<<32); // round 2
* x = x*x + y; x = (x>>32) | (x<<32); // round 3
* return (x*x + z) >> 32; // round 4
* }
* ```
*/
const squares64 = (counter) => {
let x = (counter * key) % U64;
let y = x;
let z = (y + key) % U64;
// round 1
x = (((x * x) % U64) + y) % U64;
x = (x / U32 + ((x * U32) % U64)) % U64;
// round 2
x = (((x * x) % U64) + z) % U64;
x = (x / U32 + ((x * U32) % U64)) % U64;
// round 3
x = (((x * x) % U64) + y) % U64;
x = (x / U32 + ((x * U32) % U64)) % U64;
// round 4
return ((((x * x) % U64) + z) % U64) / U32;
};
//# sourceMappingURL=rng.js.map