LuckPicker

Modulo Bias and Rejection Sampling

Why % 6 on a random byte quietly favours low numbers, and the two-line fix.

You have a source of random bytes, each uniformly distributed from 0 to 255, and you want a number from 1 to 6. The obvious answer — take the remainder after dividing by 6, and add 1 — is very slightly wrong, in a direction that always favours the low numbers.

The size of the error is small and completely real. Over a large number of rolls, faces 1 through 4 come up about 0.4% more often than faces 5 and 6, from a die that everyone would describe as fair.

The fix is two lines and is called rejection sampling. This page covers where the bias comes from, how large it is for common ranges, and why some ranges have no bias at all.

Where the bias comes from

There are 256 possible byte values, and 256 is not divisible by 6. Dividing 256 by 6 gives 42 remainder 4, which means four of the six remainders occur 43 times among the byte values and two of them occur 42 times.

Concretely: byte values 0 through 251 cover 42 complete cycles of 6. The four leftover values — 252, 253, 254 and 255 — map to remainders 0, 1, 2 and 3. So remainders 0 to 3 each have 43 byte values mapping to them and remainders 4 and 5 each have 42.

Translated to a die, faces 1 through 4 have a 43/256 chance and faces 5 and 6 have 42/256. That is 16.80% against 16.41%, when a fair die should be 16.67% for every face.

A d6 from one byte, by remainder

  • 256 ÷ 6 = 42 remainder 4.
  • Remainders 0–3: 43 byte values each → probability 43/256 = 16.80%.
  • Remainders 4–5: 42 byte values each → probability 42/256 = 16.41%.
  • Over 100,000 rolls, that is about 390 extra results in the low half.

How big the effect is, by range

The bias depends on how badly the range divides into the source space, not on the size of the range. Some ranges are perfectly clean and some are not, and the pattern is not intuitive.

Any power of two divides 256 exactly, so ranges of 2, 4, 8, 16, 32, 64 and 128 have no bias at all from a single byte. Every other range has some, and the worst cases are ranges just over a power of two — a range of 129 leaves 127 leftover values out of 256, which is the maximum possible imbalance.

  • Range 2, 4, 8, 16, 32, 64, 128: no bias — these divide 256 exactly.
  • Range 6 (a d6): the low 4 outcomes are about 0.95% more likely than the high 2.
  • Range 20 (a d20): 256 ÷ 20 = 12 remainder 16, so 16 of the 20 faces are over-represented.
  • Range 129: 127 of 256 values fall in the leftover, the worst case for a single byte.

Rejection sampling, and why it is the right fix

The fix is to throw away the leftover values. Compute the largest multiple of your range that fits in the source space — for a d6 from a byte, that is 252 — and discard any draw at or above it, drawing again until you get one below. Every remaining value now belongs to a complete cycle, so every remainder is exactly equally likely.

The cost is a small number of extra draws. For a d6 the rejection rate is 4/256, about 1.6%, so the expected number of byte draws per roll is about 1.016. For the worst single-byte case the rate approaches 50% and the expected draws approach 2, which is still trivial.

The important property is that rejection sampling gives exact uniformity, not approximate uniformity. It does not reduce the bias, it removes it, and it does so without needing to know anything about the range in advance.

Rejection sampling for a d6

  • Largest multiple of 6 at or below 256 is 252.
  • Draw a byte. If it is 252, 253, 254 or 255, discard it and draw again.
  • Otherwise take the remainder and add 1. Every face now has exactly 42/252 = 16.67%.
  • Expected cost: about 1.016 byte draws per roll.

The alternatives, and why they are worse

Scaling a float is the other common approach: draw a value between 0 and 1, multiply by the range, and truncate. This is not free of bias either — floating-point values are not uniformly spaced, so the resulting integers are very slightly uneven — though the effect is far smaller than modulo bias and is genuinely negligible for game purposes.

Drawing more bytes reduces the bias without eliminating it. Using four bytes instead of one for a d6 shrinks the imbalance to roughly one part in a billion, which is undetectable but still not zero. Rejection sampling on those four bytes costs almost nothing extra and makes it exactly zero.

There is no reason to prefer an approximation here. The correct method is short, fast, and does not require any argument about whether the residual bias matters.

Where this shows up on this site

Every integer draw on this site goes through one function that does rejection sampling, and the effect is visible in a few tool pages that mention it specifically.

The random direction generator is the interesting case in the other direction: it draws from 8 outcomes, and 8 divides 256 exactly, so it needs no rejection at all. It is one of the few tools here where the naive method would have been correct.

The dice roller needs it for every die type except d4, since 4 divides 256 and 6, 8, 10, 12 and 20 do not. A d20 built without it would over-represent 16 of its 20 faces, which is exactly the kind of defect that no amount of playing would reveal.

Frequently asked questions

How big is modulo bias for a normal die?

For a d6 from one byte, faces 1 to 4 come up about 16.80% of the time and faces 5 and 6 about 16.41%, against a fair 16.67%.

Which ranges have no bias?

Powers of two — 2, 4, 8, 16, 32, 64, 128 — because they divide 256 exactly. Everything else has some.

What does rejection sampling cost?

A small number of extra draws. For a d6 it is about 1.6% of draws discarded, so roughly 1.016 byte draws per roll.

Can I just use more bytes instead?

That shrinks the bias without removing it. Four bytes for a d6 leaves an imbalance of about one part in a billion — undetectable, but rejection sampling makes it exactly zero for almost no cost.

Is scaling a random float better?

It has a much smaller bias from floating-point spacing rather than from remainders. It is fine in practice and still not exactly uniform.

Would anyone notice this bias in a game?

Not from playing. It takes tens of thousands of recorded rolls to distinguish 16.80% from 16.67% with any confidence, which is exactly why the fix has to be in the code.

Does the direction generator need rejection sampling?

No — it has 8 outcomes and 8 divides 256 exactly, so a raw remainder is already uniform. It is one of the few tools here where the naive method is correct.

Does this apply to shuffles too?

Yes, and it compounds there. Fisher-Yates makes n−1 index draws, so a per-draw bias is applied repeatedly.

Tools that use this

Related guides

← All guides