How our random number generator works

No black box. Here is the whole pipeline, the maths behind the fairness, and the real code, in plain language first and technical detail second.

The pipeline

From hardware noise to your number, in four steps

  1. 1. Raw randomness

    We read 32-bit words from crypto.getRandomValues, your operating system’s secure generator, seeded with hardware noise.

  2. 2. Remove bias

    We turn words into a number in your range using rejection sampling, so every outcome has exactly the same chance.

  3. 3. Build the tool

    Dice, coins, lists, teams, lottery tickets and UUIDs are all built from that one trusted unbiased source.

  4. 4. Test it

    Automated statistical tests run on every change, and you can run a 10,000-roll test yourself on the home page.

The fairness trick

Why “random % 6” is not fair, and what we do instead

A computer gives us random numbers from 0 to 4,294,967,295 (that is 232 values). To roll a six-sided die you might be tempted to write random % 6. But 4,294,967,296 is not a multiple of 6: it is 715,827,882 × 6 with 4 left over. Those 4 spare values would make the first four faces very slightly more likely.

So we throw those spare values away and draw again. It happens about once in a billion rolls, costs nothing, and makes every face exactly equally likely. The effect is dramatic for small generators: squeezing a random byte (0 to 255) into 1 to 100 makes the numbers 1 to 56 about 50% more common, which you can see for yourself in the Fairness Lab.

The actual code

export function randomBelow(src: RandomSource, n: number): number {
  if (n <= 2 ** 32) {
    // Largest multiple of n that fits in 32 bits.
    const limit = 2 ** 32 - (2 ** 32 % n);
    let x = src.u32();
    while (x >= limit) x = src.u32(); // retry: these would bias it
    return x % n;
  }
  /* wider ranges: draw just enough bits, retry if outside [0, n) */
}

Simplified from src/lib/rng/random.ts. Ranges wider than 32 bits, and BigInt ranges up to 1,000 digits, use the same idea.

Tool by tool

Which method powers what

Algorithm used by each tool
ToolHow it picks
Random number generatorRejection sampling over big-integer ranges (up to 1,000 digits). No repeats: Floyd’s algorithm, then a shuffle.
Dice rollerOne unbiased integer in [1, sides] per die. Advantage rolls two and keeps the higher or lower.
Coin flipOne fresh random bit per flip, read from 32-bit words.
Name pickerUnbiased pick (or Floyd sampling for several); teams come from a Fisher-Yates shuffle dealt round-robin.
Lottery numbersFloyd sampling for each group of balls, then sorted. Odds are exact combinations, not estimates.
Random stringsOne unbiased pick from your character set per position. Entropy = length × log2(set size).
UUIDsRFC 9562: v4 has 122 random bits; v7 has a 48-bit millisecond timestamp plus random bits.
Seeded modeText → 128-bit hash (cyrb128) → sfc32 generator. Same seed, same numbers, on every device.

Honest limits

What this is, and is not

  • It is a cryptographically secure pseudo-random generator, not a physical (true random) source. It is constantly re-seeded with unpredictable hardware noise, which is why it is trusted for encryption keys. If you need certified physical randomness, use a hardware or atmospheric-noise service.
  • Seeded mode is predictable on purpose. Anyone with the seed can reproduce your numbers. Use Secure mode for anything that must stay unpredictable.
  • The animations are decoration. The spinning digits, tumbling dice, tossed coin and spinning wheel all land on a result that was chosen first. Even the animation’s randomness comes from the same secure generator, and our code is not allowed to use Math.random().
  • Lottery tools cannot improve your odds. Every combination is equally likely.

Sources

Run the test yourself

Ten thousand rolls, one chart, zero trust required.

Generate now