Random number generator

Uniform integers over an inclusive range, drawn by rejection sampling so no value is more likely than any other.

G

Google answers this query itself, and often that is enough

It is worth saying plainly rather than pretending otherwise. Search for a random number generator and Google puts its own widget at position zero, with minimum and maximum fields and a generate button, alongside a carousel of spinner, dice, coin, colour and calculator tools. We measured this in a rendered browser across fifteen search results on 5 September 2026. If you want one number between 1 and 100 and nothing else, that widget is faster than this page and you should use it.

This page exists for the cases it does not cover. It tells you where the numbers come from and how the range is mapped, which no widget does. It generates many at once rather than one. It works with the network disconnected. And it explains the two things that actually cause bugs in this area: what an inclusive bound means, and why the obvious way of forcing a random value into a range is wrong.

That is also the honest framing of the competitive position. This is not a page we expect to win a search on. It is here because the rest of the site would have a hole in it otherwise.

Inclusive bounds, and the off-by-one

Both ends of the range here are included. Set 1 and 100 and you can get 1, you can get 100, and there are 100 possible outcomes. The count of values in an inclusive range is max minus min plus one, and that plus one is where most of the errors in this subject live.

The languages do not agree with each other, which is why this needs stating. In Python, random.randint(1, 100) includes 100 but random.randrange(1, 100) stops at 99. In JavaScript, Math.random() returns a value in [0, 1), so the standard idiom needs the span to be max minus min plus one before flooring. In Go, rand.IntN(100) yields 0 to 99. In Java and .NET, the bounded integer methods take an exclusive upper bound, so an inclusive maximum of 100 means passing 101. In Rust, the range 1..=100 includes 100 and 1..100 does not.

The symptom of getting it wrong is that one specific value never appears. It is invisible in casual testing, because a value that shows up zero times out of a thousand draws looks exactly like a value that has not come up yet.

Why the modulo is biased, with the numbers

The obvious way to force a random value into a range is to take the remainder: draw a byte and compute value modulo 100. It is wrong, and for a range of 100 it is not slightly wrong.

A byte has 256 possible values. Divide by 100 and you get 2 remainder 56. So the residues 0 through 55 each have three byte values mapping onto them, and the residues 56 through 99 have only two. The result is that the numbers 1 to 56 come up half as often again as the numbers 57 to 100. That is a 50% skew, visible in a few thousand draws, from three lines of code that look perfectly reasonable.

Widening the draw shrinks the bias but never removes it. From a 32-bit value, 2^32 is 42,949,672 times 100 with 96 left over, so 96 of the four billion draws are the excess and the ratio between a common outcome and a rare one is about 1.000000023. That is unobservable. It is still not uniform, and there is no reason to accept even that when the fix is free.

The fix is rejection sampling. Compute the largest exact multiple of the span that fits in the draw, discard any value at or above it, and take the remainder of what is left. For a span of 100 drawn from 32 bits, that discards 96 values out of 2^32, so a redraw happens roughly once in 44.7 million draws. Exact uniformity costs essentially nothing, which is why we do it everywhere and why no value on this site is ever produced by a bare modulo.

Of the fifteen generators we audited on the first page of Google, one maps a 32-bit draw with a bare modulo. The bias there is cryptographically negligible because the draw is wide. The same mistake on an 8-bit draw is not negligible at all.

A random number is not a random digit string

These look like the same request and are not. A random number between 1 and 9999 has 9,999 possible values and never has a leading zero, because 0042 is not how a number is written. A random four-digit code has 10,000 possible values, from 0000 to 9999, and leading zeros are part of the value.

Which you want depends on what the value is for. A dice roll, a lottery pick or a sampled index is a number. A PIN, a one-time code, a voucher code or a fixed-width reference is a digit string, and the moment you store it as an integer the leading zeros are gone and the code no longer matches what the user was sent. Store digit strings as strings, and make the column wide enough for the fixed length.

The entropy differs too, though for these lengths not by much. A four-digit code is log2(10000) = 13.29 bits and a six-digit code is 19.93 bits. Both are far below the 64 bits that NIST SP 800-63B-4 treats as the point where a secret can stand without rate limiting, which is exactly right for one-time codes: they are safe because they expire and are rate-limited, never because they are hard to guess.

When you want speed instead of unpredictability

Everything here comes from crypto.getRandomValues(), which is the operating system CSPRNG. For a handful of numbers that is the obvious choice and the cost is irrelevant.

For a simulation drawing hundreds of millions of values, a fast deterministic generator is the right tool, and there is no shame in it. A Mersenne Twister or a PCG generator is far quicker and, crucially, seedable, so a run can be reproduced exactly. That reproducibility is a feature in a simulation and a catastrophe in a secret.

The rule is simply which property you need. If anyone would benefit from predicting the next value, use a CSPRNG. If you would benefit from replaying the sequence, use a seeded PRNG and record the seed. Never use the second where you needed the first, which is the mistake five of the fifteen audited generators make by building on Math.random().

Doing this in your own code

JavaScript (browser and Node 19+)
// Uniform integer in [min, max], both ends INCLUSIVE, no modulo bias.
function randomInt(min, max) {
  const span = max - min + 1;
  if (span <= 0) throw new RangeError("max must be at least min");

  // Largest exact multiple of span that fits in a uint32. Draws at or
  // above this are the excess that would skew the low end, so discard them.
  const limit = Math.floor(2 ** 32 / span) * span;

  const buf = new Uint32Array(1);
  let v;
  do {
    crypto.getRandomValues(buf);
    v = buf[0];
  } while (v >= limit);

  return min + (v % span);
}

randomInt(1, 100);

On Node, randomInt(min, max) from node:crypto does this rejection loop for you and excludes max. It lives on node:crypto only, not on the global crypto object, which carries just the Web Crypto surface.

Python 3.6+
import random, secrets

secrets.randbelow(100) + 1   # 1..100, unbiased, cryptographic
random.randint(1, 100)       # 1..100, BOTH ends inclusive
random.randrange(1, 100)     # 1..99, the stop value is EXCLUDED

# randint and randrange run on the Mersenne Twister. Fine for a simulation,
# never for anything anyone would want to guess.

randint and randrange disagree about the upper bound. That inconsistency is a genuine source of off-by-one bugs.

Go
import (
    "crypto/rand"
    "math/big"
)

// rand.Int returns a uniform value in [0, max) and does the rejection
// sampling internally.
n, err := rand.Int(rand.Reader, big.NewInt(100))
if err != nil {
    panic(err)
}
v := n.Int64() + 1   // 1..100

// math/rand/v2 rand.IntN(100) is much faster and is not cryptographic.
Rust
// rand = "0.9"
use rand::Rng;

// 1..=100 includes 100. 1..100 does not.
let n = rand::rng().random_range(1..=100);

On rand 0.8 the same two calls are named rand::thread_rng() and gen_range.

Java 17+
import java.security.SecureRandom;

// nextInt(origin, bound) excludes the bound, so add one for an
// inclusive maximum.
new SecureRandom().nextInt(1, 101);   // 1..100
C# / .NET 6+
using System.Security.Cryptography;

// GetInt32(fromInclusive, toExclusive). Rejection sampling is built in.
RandomNumberGenerator.GetInt32(1, 101);   // 1..100
Shell
# Bash. RANDOM is 0..32767, and 32768 = 327 * 100 + 68, so the values
# 1 to 68 come up more often than 69 to 100. Biased, and not cryptographic.
echo $((RANDOM % 100 + 1))

# Unbiased and cryptographic:
python3 -c "import secrets; print(secrets.randbelow(100) + 1)"

# From the kernel, for a power-of-two range only:
od -An -N1 -tu1 /dev/urandom

Questions

Are the minimum and maximum included?

Yes, both. A range of 1 to 100 has exactly 100 possible outcomes and both endpoints can occur. The number of values in an inclusive range is max minus min plus one.

Why not just use modulo to get a number in a range?

Because the source range is almost never an exact multiple of your span, so the low values get one extra chance each. Mapping a byte onto 1 to 100 makes 1 through 56 half as likely again as 57 through 100. Rejection sampling removes the excess before taking the remainder, and costs about one redraw in 44.7 million.

Is Math.random() good enough for a random number?

For a simulation, a shuffle in a game, or picking a colour, yes. For anything anyone would benefit from predicting, no. V8 implements it with xorshift128+, whose internal state can be recovered from a handful of outputs, after which every future value is known.

How do I generate a four-digit code with leading zeros?

Treat it as a digit string rather than a number: draw four independent digits, or draw a number from 0 to 9999 and pad it to four characters. Then store it as a string. Storing it as an integer discards the leading zeros.

Can I get the same sequence twice?

No. There is no seed input, and there will not be one, because a seedable CSPRNG output is a contradiction. If you need a reproducible sequence, you want a seeded pseudorandom generator in your own code, and you should record the seed.

Is Google's built-in random number widget fine to use?

For a casual number, yes, and it is quicker than loading any page. What it does not tell you is where the number comes from or how the range is mapped. If that matters to you, that is the reason to use a tool that publishes its method.

Related generators