Randomness and RNG
A complete walk-through of the generator behind our tool: OS-grade randomness, exact rejection sampling, and an unbiased Fisher–Yates draw without replacement. The pseudocode is on the page — check it against the running code.
Most "lucky number" tools ask you to trust them. Ours asks you to check. This article documents, in full, the design of the generator behind /tools/number-generator — every stage, every constant, and why each choice is the one a cryptographer would make. The code runs in your browser, so you can open developer tools and verify that what ships matches what's described here.
The problem has three parts, and each has a classic failure mode:
We use crypto.getRandomValues(), the browser primitive the W3C Web Cryptography specification requires to be cryptographically strong and which every major browser implements on top of the operating system's CSPRNG — getrandom() on Linux, BCryptGenRandom on Windows (details). Three properties matter:
We request 32-bit words (Uint32Array), giving 2^32 = 4,294,967,296 equally likely raw values per draw.
Folding 2^32 values onto 45 outcomes with plain % is biased, because 2^32 mod 45 = 31 — thirty-one leftover values that would favour numbers 1–31 (the full arithmetic). So we reject the leftovers (why this is exact, not approximate):
function secureIntInclusive(max): # uniform integer in 1..max
limit = 2^32 - (2^32 mod max) # for max=45: 4,294,967,265
repeat:
x = one fresh 32-bit word from crypto.getRandomValues
until x < limit
return (x mod max) + 1
For max = 45, limit is 4,294,967,265 = 45 × 95,443,717 — a perfect multiple of 45 — so each accepted word maps onto every number exactly 95,443,717 ways: probability exactly 1/45 each. The rejection zone is just 31 values wide, so a retry happens about once per 4,294,967,296 / 31 ≈ 139 million calls. Exactness costs essentially nothing at 32 bits.
A lottery line needs, say, 6 distinct numbers from 45, with every one of the 8,145,060 possible sets equally likely. The tempting shortcut — "pick a number, re-roll if you've seen it" — works but wastes draws; the classic correct tool is the Fisher–Yates shuffle in its modern form, published as Durstenfeld's Algorithm 235 (CACM, 1964): walk through an array once, swapping each position with a uniformly chosen position at or after it. Run over a whole array it produces every permutation with equal probability; we only need the first 6 positions, so we stop after 6 swaps — a partial Fisher–Yates:
function secureDraw(pool, count): # e.g. pool=45, count=6
balls = [1, 2, 3, ..., pool]
for i from 0 to count-1:
j = i + secureIntInclusive(pool - i) - 1 # uniform in i..pool-1
swap balls[i], balls[j]
return sort(balls[0..count-1])
Why this is unbiased: at step i, every ball not yet drawn has an equal chance of being swapped into position i, because secureIntInclusive(pool − i) is exactly uniform (stage 2) over the remaining slots. Multiply along the steps and every ordered sequence of 6 distinct balls has probability 1/45 × 1/44 × 1/43 × 1/42 × 1/41 × 1/40 — so after sorting, every 6-number set has probability exactly 720 × (1/5,864,443,200) = 1/8,145,060. The same probability every real lottery machine aims for.
Games with a supplementary pool (a separate "Powerball"-style barrel) get an independent second secureDraw over that pool, mirroring how the physical draw uses a second machine.
crypto.getRandomValues → raw 32-bit words (OS CSPRNG)
secureIntInclusive(max) → exact uniform 1..max (rejection sampling)
secureDraw(pool, count) → unbiased distinct set (partial Fisher–Yates)
Three stages, each with a one-line correctness argument, composed so that the final claim — every possible line is equally likely — follows from the stages rather than from our say-so.
We mean this literally. The pseudocode above corresponds line-for-line to the shipped implementation. Ways to check it:
secureIntInclusive and secureDraw. Compare against this page.One honest caveat to close on: an unbiased generator gives you fair numbers, not better ones. Every line — quick pick, birthday numbers, or the output of this pipeline — has the same 1 in 8,145,060 chance in a 6-from-45 game. What this generator guarantees is narrower and more defensible: nobody, including us, can predict or skew what it gives you.
Last verified: 2026-08-29