DiceDecide

How to Verify a Random Number Generator Actually Includes Both End Values

Be the first to rate this page.

A range documented as inclusive on both ends, such as 1 through 100, should produce the value 1 and the value 100 at the same roughly 1% rate as every other integer in between; after only 50 calls, though, a correctly working generator still has about a 60.5% chance of never once showing a specific endpoint, so a short manual check cannot tell a working implementation from a broken one.

A worked example with numbers

RANDOM.ORG’s integer-generator API documents its min and max parameters as both inclusive, meaning a request for integers between 1 and 100 should be able to return 1 or 100 exactly as often as 50. Suppose a developer calls that endpoint, or an equivalent locally implemented range function, 50 times and never once sees the value 100 appear. Under a correctly implemented inclusive range of 100 possible values, the probability of missing one specific value across 50 independent draws is (1 − 1/100)^50 ≈ 60.50% — meaning a perfectly correct generator produces exactly this “missing endpoint” result more than three times out of five. That single absence is not evidence of a bug; it is the expected, ordinary behaviour of a working 100-value range over a short run.

How to set the rule before the result

To responsibly test whether an endpoint is truly included, run enough trials that a genuinely correct implementation would be very likely to show it, then check specifically for its presence — not its frequency. For a range of 100 possible values, reaching a 99% chance that a working generator shows a specific value at least once needs roughly n = ln(0.01) ÷ ln(1 − 1/100) ≈ 459 calls; for a small six-value range, such as the faces of a die, the same 99% threshold needs only about n = ln(0.01) ÷ ln(1 − 1/6) ≈ 26 calls, since a smaller range makes every individual value proportionally more likely to appear on any given trial. Log every returned value in a plain list rather than only a running count, then check that the maximum and minimum recorded values equal the requested bounds exactly.

Common mistakes that change the odds or the process

A common mistake is concluding a range is exclusive at one end after a handful of calls never produce that endpoint, when the math above shows this is ordinary behaviour at small sample sizes rather than a sign of a bug. A second, opposite mistake is trusting a generator’s documentation without ever testing it — a function documented as producing “1 to 100 inclusive” can still contain an off-by-one error, such as `Math.floor(Math.random() * 100) + 1`, which correctly reaches 100 but a very similar-looking `Math.floor(Math.random() * 99) + 1` would silently cap out at 99 and never reach 100 at all, an error invisible without deliberately checking the maximum observed value. A third mistake is testing only the endpoints and assuming the interior values are automatically fine; an implementation can correctly include both endpoints while still weighting some interior values unevenly, which needs the separate chi-square-style check described elsewhere on this site, not just an endpoint scan.

Where this method stops being appropriate

Endpoint testing only confirms that the extreme values of a stated range are reachable at all; it says nothing about whether they are reachable at the correct proportional rate, which needs the fuller goodness-of-fit approach used to test a die. It also assumes the documented range itself is accurate — a service that changes its bounds without updating its documentation will pass an endpoint test against the wrong numbers. And this check applies specifically to integer ranges with a small, fixed number of possible values; it does not extend cleanly to a continuous decimal or percentage generator, where no single output value is expected to repeat at all, endpoints included.

How the random source fits into the rule

RANDOM.ORG’s HTTP API documentation documents RANDOM.ORG’s integer-generator range as inclusive on both the min and max parameters, the exact contract this kind of boundary test is designed to confirm; MDN’s Math.random reference documents the half-open 0-to-1 interval Math.random() itself returns, which is the underlying source most local range-conversion code like the example above has to convert correctly.

How many calls it takes to confirm an endpoint, by range size

Range sizeCalls for 90% confidenceCalls for 99% confidence
6 (a die)~13~26
10~22~44
100~230~459
1,000~2,302~4,603

Each figure comes from n = ln(1 − confidence) ÷ ln(1 − 1/range), rounded up. Doubling the confidence target roughly doubles the required calls, but widening the range multiplies the requirement almost directly — a 1,000-value range needs about ten times the calls a 100-value range does, for the same confidence level, because a single value is proportionally ten times rarer.

A second case: catching an off-by-one bug in a d20 roller

Suppose a die-rolling function is meant to return 1 through 20 inclusive but actually implements `Math.floor(Math.random() * 19) + 1`, silently capping its output at 19. Detecting this by never seeing a 20 needs enough calls that a correct d20 implementation would be very unlikely to also miss 20 by chance: at 99% confidence, that is roughly n = ln(0.01) ÷ ln(1 − 1/20) ≈ 90 calls. Fewer than that, and a perfectly working d20 can plausibly “miss” 20 by pure chance often enough that the absence alone would not distinguish a working implementation from the off-by-one bug.

What an endpoint check does not verify

Confirming that 1 and 100 both appear across enough trials only checks reachability, not the shape of the whole distribution — a generator that returns 1 and 100 correctly but silently returns 50 twice as often as every other value would pass an endpoint check while still being badly biased in the middle of its range. A full check needs both the endpoint scan described here and a distribution check, such as the chi-square approach used to test a physical die, run across enough calls to give each individual value a reasonable expected count. Treating a clean endpoint result as a complete fairness certificate skips the half of the range that most needs checking.

A related edge case worth checking separately is a single-value range, where min and max are equal — such as requesting an integer “between 7 and 7.” A correct implementation should always return exactly 7 with no randomness involved, rather than throwing an error or silently treating the request as some wider default range; testing this case takes exactly one call, since there is only one possible correct output to check for. A range starting away from 1, such as 7 through 42, needs the same two-endpoint verification as a range starting at 1 — nothing about the boundary-testing method above depends on the range beginning at any particular number, only on its width.

Related DiceDecide tools

Enter your values, review the result, then use it with confidence.

Rate this page

Be the first to rate this page.