DEV Community

Michael Lip
Michael Lip

Posted on • Originally published at zovo.one

Random Numbers in Software Are Not Random

Math.random is not random. It is a deterministic algorithm that produces numbers that look random but are entirely predictable if you know the seed. For most applications this is fine. For security it is catastrophic.

Pseudorandom vs. cryptographically secure

Math.random() uses a pseudorandom number generator (PRNG). It is fast and produces a uniform distribution, but the sequence is deterministic. Given the same seed, it produces the same sequence every time.

// Pseudorandom - fine for games, simulations, UI
Math.random(); // 0.0 to 1.0

// Cryptographically secure - required for security
crypto.getRandomValues(new Uint32Array(1))[0];
Enter fullscreen mode Exit fullscreen mode

crypto.getRandomValues() uses the operating system's cryptographic random source (like /dev/urandom on Linux). It is slower but unpredictable, making it suitable for generating passwords, tokens, and encryption keys.

Generating random integers in a range

The naive approach has a subtle bias:

// Biased - do not use for anything important
Math.floor(Math.random() * (max - min + 1)) + min;
Enter fullscreen mode Exit fullscreen mode

The bias comes from the fact that Math.random() produces a 64-bit float, and the modulo operation (Math.floor(... * range)) does not distribute evenly when the range is not a power of 2.

For unbiased random integers:

function secureRandomInt(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);
}
Enter fullscreen mode Exit fullscreen mode

The rejection sampling (while (value >= maxValid)) eliminates the modulo bias entirely.

Common use cases

  • Passwords: Use crypto.getRandomValues with a character set
  • UUIDs: Use crypto.randomUUID() (v4)
  • Shuffling: Fisher-Yates shuffle with random swaps
  • Sampling: Random selection from a dataset
  • Testing: Generating test data with known distributions

For generating random numbers with various distributions and ranges, I built a generator at zovo.one/free-tools/number-generator. It supports integers, decimals, custom ranges, and batch generation.


I'm Michael Lip. I build free developer tools at zovo.one. 500+ tools, all private, all free.

Top comments (0)