Random Number Generator

Uniform random numbers from crypto.getRandomValues by rejection sampling, with the modulo bias it avoids spelled out for your range.

Enable JavaScript to customise; default output below.

Both ends are included, so 1 to 100 can produce 1 and 100.

Live preview random-numbers.txt
generated in your browser

Range                 1 to 100
How many              6
Repeats               not allowed

The bias this avoids
  byte % range        up to 50.00% more likely for the low end
  method used         rejection sampling, so every value is equally likely
  source              crypto.getRandomValues

Taking a random byte modulo 100 would not be uniform: 256 is not a
multiple of 100, so the first values in the range get an extra chance
and come up about 50.00 percent more often. Rejection sampling discards
the values in the short final block, which costs a few bytes and buys a
flat distribution.

`Math.random` is not suitable for anything that functions as a secret.
It is a fast pseudo-random generator, its internal state can be
recovered from enough of its output, and nothing about it is guaranteed
between engines. For a shuffle in a game or a test fixture it is fine.

`crypto.getRandomValues` is the right source for a token, a password, a
draw with a prize or anything an adversary would benefit from
predicting. It is in every browser and there is no reason to use
anything else for those.

Random does not mean evenly spread. Six numbers from 1 to 100 will often
include two close together and leave a gap of thirty, and a sequence
that looks too tidy is usually the suspicious one. Clustering is what
randomness looks like.

With no repeats this is a draw rather than a sequence of rolls: the
whole range is shuffled with Fisher-Yates and the first few are taken,
which is the correct way to do it and the reason the range has to be
small enough to enumerate.

A random number is not an identifier. For an id, use a UUID or a long
random string: the point of those is that the space is large enough that
a collision is not a practical concern, which a range of a hundred is
not.

This cannot be seeded, on purpose. A seeded generator produces a
repeatable sequence, which is useful in testing and fatal anywhere the
numbers are supposed to be unpredictable, and offering both in one tool
invites using the wrong one.

Output is valid and updates as you type.

bytes[ 0 ] % 100 is not a random number from 1 to 100. It is a number from 1 to 100 where the first 56 values are 50 percent more likely than the rest, because 256 is not a multiple of 100.

That is modulo bias, and it is the standard bug in code that generates random numbers from bytes. The fix is rejection sampling: throw away the values that fall in the short final block and draw again. It costs a few discarded bytes and buys a flat distribution, and it is what this does.

The source is crypto.getRandomValues rather than Math.random, and the difference matters for anything an adversary would benefit from predicting.

How to use

  1. Set the range. Both ends are included.
  2. Choose how many, and whether repeats are allowed.
  3. Read the bias figure to see what the naive method would have done to this range.

Example

Six different numbers from 1 to 100:

11, 15, 35, 45, 64, 97

Range                 1 to 100, 100 possible values
How many              6, all different
Smallest              11
Largest               97
Mean                  44.50
  expected mean       50.50

The bias this avoids
  byte % range        up to 50.00% more likely for the low end
  method used         rejection sampling, so every value is equally likely
  source              crypto.getRandomValues

The mean of six numbers is 44.5 against an expected 50.5, and that is what six draws look like. Reading a bias into a sample that small is the other half of this subject.

Pitfalls

Modulo bias is worst where the range is close to the pool. For a die from one byte it is about 2.4 percent. For a range of 200 it is a factor of two: the first 56 values get twice the chance. The figure for your range is printed above.

Math.random is not for secrets. It is a fast pseudo-random generator, its internal state can be recovered from enough output, and nothing about it is specified between engines. For a game shuffle or a test fixture it is fine; for a token, a password reset or a prize draw it is not.

Clustering is what randomness looks like. Six numbers from 1 to 100 will often have two close together and a gap of thirty. A result that looks evenly spread is the suspicious one, and people asked to “act random” produce exactly that too-tidy pattern.

A repeat arrives sooner than you expect. Drawing from 100 values with repeats allowed, there is a better than even chance of a duplicate by the thirteenth draw. That is the birthday problem, and it is why a short random number is a poor unique identifier.

A random number is not an id. For an identifier use a UUID or a long random string, where the space is large enough that collisions are not a practical concern. A range of a hundred is not that.

Sorting does not change the draw. The sort here is presentation. If a list of “random” numbers has to come out in order, that is a display choice and not part of the randomness.

There is no seed, on purpose. A seeded generator gives a repeatable sequence, which is exactly right in a test and exactly wrong anywhere the numbers must be unpredictable. Offering both in one tool invites reaching for the wrong one.

Compatibility

Everything runs in the browser: nothing is uploaded and nothing is stored, and no number is ever sent anywhere.

crypto.getRandomValues is in every browser and has been for over a decade. The integer is drawn by rejection sampling over as many bytes as the range needs, so a range of a thousand uses two bytes and the distribution stays flat.

A draw with no repeats shuffles the whole range with Fisher-Yates and takes the first few, which is the correct method and the reason the range has to be small enough to enumerate. Past a million the tool says so rather than trying.

The flatness is checked in the test suite over 120,000 rolls of a six-sided range, with a tolerance tighter than the bias a modulo would have introduced. The rejection path itself is checked with a fixed byte sequence.

At build time there is no randomness available, so the page ships with a placeholder and the numbers appear when it loads. Numbers baked into the HTML would be identical for every visitor.

Frequently asked questions

Is this cryptographically secure?
The source is, and crypto.getRandomValues is the right primitive for a token or a key. Whether the whole thing is secure depends on what you do with the output, and pasting it into a web page is not part of a secure workflow.
How do I do this in JavaScript?
crypto.getRandomValues( new Uint32Array( 1 ) )[ 0 ] for a raw value, then reject anything at or above Math.floor( 2 ** 32 / range ) * range before taking the modulo. Skipping the rejection step is the bug this page is about.
Can I use it for a giveaway?
The randomness is sound. For anything with a prize, a witness and a record of the entrants at the time of the draw matter more than the generator, because that is what makes the result defensible.
Why does the same number come up twice?
Because repeats are allowed, and they are more likely than intuition suggests. Turn on “no repeats” for a draw where each value can only appear once.
What about very large ranges?
Up to a trillion here. Past that, use the number base converter, which works through BigInt and is exact at any size; a random value beyond 2^53 cannot be represented by a JavaScript number anyway.
Weekly drops

New tools, when there are new tools

One email when something worth using ships. No schedule to fill, so no filler.

Your address goes nowhere else, and one click unsubscribes.