In 1955, the RAND Corporation published "A Million Random Digits with 100,000 Normal Deviates," a book containing exactly what the title describes. They generated the numbers using an electronic roulette wheel connected to a computer. The book is still in print. Its Amazon reviews are legendary.
Generating random numbers seems trivial until you understand the requirements. For a dice game, Math.random() is fine. For a lottery, it's legally insufficient. For cryptography, it's dangerously inadequate.
The modulo bias problem
A common pattern for generating a random integer from 1 to 6:
Math.floor(Math.random() * 6) + 1
This works because 6 divides evenly into the possible outputs. But what about generating 1 to 5? Math.random() returns a floating-point number with 2^53 significant bits. 2^53 is not evenly divisible by 5. Some values (1 and 2) are very slightly more probable than others (3, 4, 5).
The bias is tiny (on the order of 10^-16) and irrelevant for most applications. But for cryptographic applications or high-stakes gambling, even tiny biases can be exploited.
The unbiased approach is rejection sampling: generate a random number in a range that's a multiple of your desired range, and reject values outside the desired range.
function unbiasedRandom(min, max) {
const range = max - min + 1;
const bytesNeeded = Math.ceil(Math.log2(range) / 8);
const maxValid = Math.floor(256 ** bytesNeeded / range) * range;
let value;
do {
const bytes = crypto.getRandomValues(new Uint8Array(bytesNeeded));
value = bytes.reduce((acc, b, i) => acc + b * 256 ** i, 0);
} while (value >= maxValid);
return min + (value % range);
}
Distribution matters
Not all random numbers should be uniformly distributed. Many real-world phenomena follow non-uniform distributions:
Normal (Gaussian): Heights, test scores, measurement errors. Generated from uniform random numbers using the Box-Muller transform.
Exponential: Time between events (customer arrivals, server requests). Generated as -ln(U)/lambda where U is uniform.
Poisson: Count of events in a fixed interval. Generated by summing exponentials until they exceed 1.
A random number generator that only produces uniform distributions is incomplete. For simulation, statistical testing, and modeling, you need distribution options.
Seed-based reproducibility
For scientific computing and testing, you need reproducible random sequences. Same seed produces the same sequence. This lets other researchers reproduce your Monte Carlo simulation or lets a test suite produce deterministic results.
JavaScript's Math.random() doesn't support seeding. You need a library (like seedrandom) or a custom PRNG implementation. The Mersenne Twister is the standard for scientific applications: long period (2^19937 - 1), good statistical properties, and fast.
I built a random number generator at zovo.one/free-tools/random-number-generator that supports integer and floating-point ranges, multiple distributions, batch generation, and duplicate prevention for sampling without replacement. It uses cryptographic randomness for security-sensitive use cases.
I'm Michael Lip. I build free developer tools at zovo.one. 500+ tools, all private, all free.
Top comments (0)