Random Number Generator
Every draw from this random number generator happens in your browser by crypto.getRandomValues, debiased by rejection sampling, with the arithmetic shown.
Notice: For entertainment and informational purposes only. Generated numbers are random and do not improve the odds of winning. CompoundPage is not affiliated with or endorsed by Powerball, Mega Millions or any lottery operator. Lottery participation is restricted by age and by law. If you or someone you know has a gambling problem, call 1-800-GAMBLER.
5 random numbers
- Where this came from Your browser's cryptographically secure generator, not Math.random(). V8 implements Math.random() with xorshift128+, whose internal state can be recovered from a short run of its own output. That is fine for shuffling a playlist and unusable for anything anybody would call a secret.
- crypto.getRandomValues()
- Debiased by 2^32 is not a multiple of 100, so 96 values would get one extra chance each under plain modulo. Those words are drawn again instead of used, which costs 0.000002% of draws and makes every outcome exactly equally likely.
- rejection sampling, discarding 96 of 2^32 words
- Skew avoided How much more likely the favored outcomes would have been under
getRandomValues() % range. For small ranges it is far below anything observable; the reason to do it properly is that the same line of code is wrong by nearly 2x for ranges near 2^31, and nobody rewrites it when the range changes. - 0.00000233%
- Range size
- 100 values
- Entropy 5 independent draws from 100 values. For comparison, a six-character lowercase password carries about 28 bits and a four-word passphrase from a 7,776-word list about 51.
- 33.2 bits
- The numbers in the HTML were drawn once when the site was built, so without JavaScript every visitor sees the same ones. With JavaScript on, which is the normal case, the draw happens in your own browser, from your own generator, and nothing is sent anywhere.
How the Random Number Generator works
A random number generator has one job and two ways to get it wrong. This generator does neither, and shows the work for both.
The first is the source. Most generators call Math.random(), which in V8 is xorshift128+: a fast, statistically respectable pseudo-random generator whose internal state can be reconstructed from a short run of its own outputs. For picking a card or shuffling a playlist that is fine. For anything that stands in for a secret, or anything whose fairness someone might later want to defend, it is not, and the fix costs nothing: crypto.getRandomValues() is in every browser, draws from the operating system's cryptographically secure pool, and is exactly as easy to call.
The second is the arithmetic, and it is the one almost everybody gets wrong. Having drawn a 32-bit word, you need a number in some smaller range, and the obvious line is word % range. That is biased. 2^32 is 4,294,967,296, and unless your range divides it exactly, the division leaves a remainder, and every outcome inside that remainder gets one extra chance. For a range of 100 the skew is about one part in forty million, which nobody will ever see. For a range just above 2^31 one outcome is nearly twice as likely as another. The same line of code produces both, and nobody revisits it when the range changes.
The fix is rejection sampling. Work out the largest multiple of the range that fits in 2^32, and throw away any word at or above it. Every surviving word maps to exactly one outcome, and every outcome has exactly the same number of surviving words behind it, so the result is uniform by construction rather than by approximation. The cost is the occasional extra draw. For every range, the chance of needing one is below one in two, so the loop effectively always terminates on its first or second try. The result above reports exactly how many words were discarded for your range and how much skew that removed.
Drawing without repeats is a different algorithm again. The naive version draws and retries on a collision, which is fine for 6 from 49 and degenerates badly as the draw approaches the range size. Picking 20 from 20 that way takes an unbounded number of attempts to land the last value. This uses a partial Fisher-Yates shuffle instead, which does exactly one draw per result regardless. It also does it over a sparse map rather than a dense array, so asking for 5 distinct numbers between 1 and 10,000,000 touches five slots rather than allocating ten million.
One honest note about how the numbers reach you. The site is static, so the numbers baked into the HTML were drawn once at build time. With JavaScript enabled the page redraws immediately in your own browser and nothing leaves your machine. With JavaScript disabled you are looking at that single build-time sample, and so is everyone else. That is why it is labeled rather than presented as a fresh draw.
Random Number Generator FAQ
Is this actually random?
It is as random as your operating system can make it. crypto.getRandomValues() draws from the OS entropy pool (on Linux that is getrandom(2), on macOS and Windows the equivalent system CSPRNG), which is seeded from hardware sources and is the same facility used to generate cryptographic keys. It is not a hardware true-random source like atmospheric noise, and for any purpose short of a licensed lottery draw the difference does not exist.
Why not just use Math.random()?
Because its output is predictable to anyone who sees enough of it. V8's Math.random() is xorshift128+ with 128 bits of state, and recovering that state from a sequence of outputs is a documented exercise, after which every future value is known. It is also seeded per-page and makes no promise of uniqueness across tabs. None of that matters for a game and all of it matters the moment the number is used for anything else.
What is modulo bias, in one sentence?
Taking a random 32-bit word modulo your range gives the first 2^32 mod range outcomes one extra chance each, because 2^32 does not divide evenly by most numbers. The result above shows exactly how many words that is for your range and how much likelier it would have made the favored outcomes.
Does the bias actually matter for small ranges?
No, and it is worth saying so plainly: for a range of 6 or 100 the skew is smaller than one part in ten million and no experiment you could run would detect it. The reason to debias anyway is that the correct version is three lines, and the incorrect one is quietly wrong by up to 2x for large ranges. The only question is whether the range ever changes.
Can I use this to generate a password?
The source is right for it: this is the same API a password manager uses, and because it runs in your browser nothing is transmitted or logged. But a string of digits is a poor password whatever generated it. What matters is entropy, reported above in bits: aim for 70 or more, which means a long passphrase or a long random string over a large character set rather than numbers in a range.
Do the same numbers come up for everyone?
Only if JavaScript is off. The site is statically hosted, so the numbers in the served HTML were drawn once when it was built and that single sample goes to every visitor who never runs the script. With JavaScript on the page redraws in your browser on load and on every press, from your own generator.
Why does picking without repeats sometimes refuse?
Because it cannot be done. Asking for 10 distinct numbers between 1 and 6 has no answer, so the page says so rather than returning six numbers or looping forever. Allow repeats, widen the range, or ask for fewer.
Ranges and games
- Random number generator 1 to 10
- Random number generator 1 to 100
- Random number between 1 and 6
- Random number from 1 to 2
- Random number generator 1 to 1000
- Random number generator 1-49: pick 6
- Bingo caller
- Lottery number generator