Are Online Dice Rollers Actually Random?

Anyone who has watched a virtual d20 hand them three 1s in a row has wondered whether the thing is honest. It is a fair question, and unlike most gaming arguments it has a checkable answer. Digital dice can be genuinely fair, can be subtly unfair in a way nobody notices, and are — with a bit of care — meaningfully fairer than the plastic dice in your bag.

The two random number generators in your browser

Every browser ships two sources of randomness, and the difference between them matters.

Math.random() is the one almost everybody uses. It is a pseudo-random generator: an algorithm that starts from a seed value and produces a stream of numbers that look random but are entirely determined by that seed. Modern browsers use an algorithm called xorshift128+, and it is fast, statistically decent, and completely unsuitable for anything where predictability is a problem. Its output stream is not designed to be unguessable, the seeding is not guaranteed to be strong, and the specification explicitly declines to require cryptographic quality.

For a dice roller nobody is betting on, Math.random() is probably fine. But “probably fine” is a strange thing to build a fairness feature on when the alternative is free.

crypto.getRandomValues() is the other one. It is a cryptographically secure generator, seeded from the operating system’s entropy pool — timing jitter, hardware noise, interrupt patterns, and on most modern machines a dedicated hardware instruction. Its output is required to be unpredictable even to someone who has seen all the previous output. It is the generator used to create encryption keys and session tokens.

It is one line of code away, and it is what this site uses.

Modulo bias: the mistake that actually breaks dice

Having good raw randomness is only half the job. The generator hands you a number in some large range, and you have to squeeze it into 1–20 without distorting it. The obvious way to do that is the remainder operator, and the obvious way is wrong.

Here is the problem in its clearest form. Suppose you have a random byte — a number from 0 to 255, 256 possibilities — and you want a d6:

roll = (byte % 6) + 1

256 does not divide by 6. It divides 42 times with 4 left over. So the values 0, 1, 2 and 3 each get 43 chances of appearing, while 4 and 5 get only 42. The result: faces 1 through 4 come up 16.80% of the time and faces 5 and 6 come up 16.41%. A small bias, but a permanent one that no amount of good randomness upstream will fix.

Now do the same for a d100 with a single byte. 256 ÷ 100 is 2 remainder 56, so results 1 to 56 have three chances and 57 to 100 have two. Low results become 50% more likely than high ones. On a percentile roll-under system that is not a rounding error, it is a broken game.

Rejection sampling: the fix

The correct fix is unglamorous. You define the largest multiple of your die size that fits in the generator’s range, throw away any number above it, and draw again.

For a d6 from a byte: 42 × 6 = 252, so you accept 0–251 and discard 252–255. Discarded values happen 1.6% of the time, you simply ask for another byte, and every face ends up at exactly 16.667%. The loop is not guaranteed to terminate in any fixed number of steps, but the chance of needing more than a few attempts is astronomically small.

This is exactly what the roller here does, using 32-bit values rather than bytes. The how it works page shows the actual function, all fifteen lines of it.

The reason to spell this out is that it is the part nobody checks. A roller can advertise “cryptographically secure” truthfully and still hand you a biased d100 because the last step was written carelessly. The generator and the range reduction are two separate opportunities to get it wrong.

What about the animation?

A reasonable suspicion: does the 3D animation decide the result? Could a roller pick a nice-looking number and animate toward it?

It could, and that is why the ordering matters. Here, the number is drawn first — from the secure generator, with rejection sampling — and the animation is then told which face to land on. The physics you see is decorative. It has no input into the result and cannot change it.

That is the honest architecture and it is also the only sane one, because animating true tumbling physics and reading the result off the resting face would be less fair, not more: floating-point physics simulations have their own biases, and a simulated die that lands on its point or clips through the table has to be handled somehow.

How physical dice compare

The assumption underneath the whole question is that a real die is perfectly fair and the computer has to live up to it. That assumption does not survive contact with the evidence.

Ordinary dice have hollow pips. The 6 face has six cavities of plastic removed; the 1 face has one. That leaves the 6 side fractionally lighter, and a fractionally lighter side is fractionally more likely to end up on top. Casino dice solve this by backfilling the pips with paint of the same density — which tells you the manufacturers consider the effect real enough to engineer around.

Rounded edges and moulding tolerances vary. Injection-moulded dice have a sprue mark, slightly unequal faces, and corner radii that differ from die to die. Precision casino dice are machined flat to within a fraction of a millimetre and cost accordingly, and even they are retired after a few hours of table use because the edges chip.

Polyhedral dice are worse than d6s. A d20 has twenty faces that all need to be identical, and mass-market dice are not made to casino tolerances. Enthusiasts periodically run large-scale roll tests on popular dice and find measurable deviations. Whether those deviations matter at your table is another question — they are usually smaller than the number of rolls in a campaign can detect.

And how you roll matters. A die dropped two inches onto a soft mat is barely randomised at all. Persi Diaconis, the mathematician best known for showing that coin flips are slightly biased toward the starting side, has made related arguments about dice: a throw is a physical process, and short, gentle throws do not fully scramble the starting state. A d4 in particular barely tumbles.

None of this makes physical dice unusable. It makes them approximately fair — good enough for a game, and not the perfect benchmark they get treated as.

Can you verify a roller yourself?

Partly, and it is worth knowing the limits.

What you can do: roll a few thousand times and count the results. If a d20 face appears 700 times in 10,000 rolls when you expect 500, something is wrong. A chi-squared test tells you whether the deviation you see is bigger than chance can explain. On an open-source roller you can also just read the code, which is the more direct route.

What you cannot do: conclude anything from twenty rolls, or from a bad evening. Randomness produces streaks. In a hundred d20 rolls, seeing the same number four times running is unremarkable; the human sense of what “random” should look like is systematically wrong, and it is wrong in the direction of expecting too much evenness.

What to look for in a roller: it should say which generator it uses, say how it reduces the range, and be honest about the animation being cosmetic. If a site says nothing about any of this, it is not necessarily unfair — but you have no way of telling.

The short answer

A well-built online roller uses a cryptographically secure generator, reduces its output with rejection sampling rather than a bare remainder, and draws the number before it animates anything. Done that way it is at least as fair as a physical die and probably fairer, because it has no centre of mass to be off.

Done carelessly — Math.random() and a modulo — it is usually close enough to fair that you would never notice, which is exactly what makes carelessness worth avoiding. If you want the detail on how the rolls here are generated, including the actual code, it is on the how it works page.

Roll the dice from this guide

← All guides