How Random Is "Random"?
The Mathematics Behind WheelNamer
WheelNamer · Published 2025
Press a button. A wheel spins. A coin flips. A decision is made in an instant. But what is actually happening under the hood — and how can we prove it is genuinely fair?
Want to just run the test and see the results for yourself?
For the math teachers, the boffins, the scientists, and insomniacs looking for a good read to fall asleep to, the full mathematics, step-by-step proofs, and all the good stuff awaits below.
Skip straight to the mathematics ↓Why can we simulate 100,000 spins in seconds?
This question catches people off guard, so it is worth answering before anything else.
When you spin a wheel on WheelNamer, two things happen that look connected but are mathematically separate:
Step 2 — the animation plays. This takes 8–12 seconds.
The animation and the decision are separate things. The moment you click spin, the computer generates a random number and maps it to an outcome — this is the act of randomness itself. The wheel then spins for the full duration, giving that decision a satisfying visual form. Nothing is rigged or predetermined; the randomness is genuine and happens before the wheel stops.
A simulation skips the animation entirely. Instead of spin → wait → record → repeat, it does generate number → record → repeat, essentially instantly. A modern computer can call Math.random() roughly 100 million times per second. So 100,000 simulated spins takes about one millisecond.
The engine: one number decides everything
Every random outcome on WheelNamer — Yes/No, Heads/Tails, or any wheel — begins with a single function call:
Math.random()
This returns a decimal number between 0 (included) and 1 (not included). Every value in that range is equally likely. That single number is then mapped to an outcome.
Yes / No — step by step
The question: how do we guarantee each outcome is exactly 50%?
let r = Math.random() // r is somewhere between 0 and 1
if (r < 0.5) {
result = "Yes"
} else {
result = "No"
}Picture the range 0 to 1 as a ruler, one metre long. The condition r < 0.5 covers the left half (0 to 0.5). The condition r ≥ 0.5 covers the right half (0.5 to 1). Both halves are exactly the same length.
0 ──────────────── 0.5 ──────────────── 1 │ Yes │ No │ │ 50.00% │ 50.00% │
P(Yes) = 0.5 ÷ 1.0 = 50% P(No) = 0.5 ÷ 1.0 = 50% Total = 100% ✓
No magic. Just geometry on a number line.
Heads / Tails — identical logic
Heads/Tails uses the exact same binary split. The coin animation — the spin, the arc, the landing — plays out in full while the outcome has already been determined by the random number generated the moment you clicked.
Three equal options — a three-way split
let r = Math.random()
if (r < 0.3333) { result = "A" }
else if (r < 0.6667) { result = "B" }
else { result = "C" }0 ─────── 0.333 ─────── 0.667 ─────── 1 │ A │ B │ C │ │ 33.33% │ 33.33% │ 33.33% │
P(A) = 0.3333 ÷ 1.0 = 33.33% P(B) = 0.3333 ÷ 1.0 = 33.33% P(C) = 0.3333 ÷ 1.0 = 33.33% Total = 100% ✓
The wheel — any number of segments
let r = Math.random() let result_index = Math.floor(r * numberOfSegments)
Multiplying by n stretches the ruler from 1 to n units, and Math.floor chops it into n equal integer buckets. Each bucket is length 1 out of n total, so P = 1/n exactly. For any number of equal segments, every entry always has the same probability.
Weighted segments
If segments have different sizes — say one option has twice the arc of another — the probability is simply that segment's weight divided by the total of all weights. The mathematics is identical in every case: geometry on a number line, with boundaries set to match the intended probabilities exactly.
What generates the number?
Math.random() uses an algorithm called xorshift128+, which Chrome, Firefox, Edge, and Safari all implement. It works in three steps:
- Seed — a starting number drawn from unpredictable system sources: CPU timing down to nanoseconds, the OS entropy pool.
- Transform — a series of fast bit-manipulation operations converts the seed into an output number.
- Chain — that output becomes the seed for the next call.
The sequence will not repeat until 2¹²⁸ calls have been made — at 100 million calls per second, cycling through the full sequence would take roughly 10²² years.
The Law of Large Numbers
The more trials you run, the closer your observed results get to the true probability. For a fair coin (P = 0.5), the expected deviation after n trials is σ = 0.5 / √n:
n = 100: σ = ±5.0% n = 1,000: σ = ±1.6% n = 10,000: σ = ±0.5% n = 100,000: σ = ±0.16%
This is why Chart 3 in the simulator looks like two identical bars at 100,000 spins. They are not identical — one might be 50,083 and the other 49,917 — but at that scale, 0.16% is visually indistinguishable from zero. That is the proof of fairness.
The chi-squared test — formal proof of fairness
The chi-squared (χ²) test asks: "Are these results consistent with what a truly fair source would produce?" Any result below the critical value means there is no statistically significant evidence of bias — the observed deviation is consistent with natural random variation.
Why each spin is independent
The random number generator has no usable memory of past outputs. Landing on "Yes" ten times in a row does not make "No" more likely. The probability resets to exactly 50% for every single spin. This is the memoryless property, and it is mathematically guaranteed by the structure of xorshift128+. There are no hot streaks. No due outcomes. No patterns to detect or exploit.
Summary
| Feature | Method | Calculation | True probability |
|---|---|---|---|
| Yes / No | r < 0.5 | 0.5 ÷ 1.0 | 50% each |
| Heads / Tails | r < 0.5 | 0.5 ÷ 1.0 | 50% each |
| Three equal options | floor(r × 3) | 0.333 ÷ 1.0 | 33.33% each |
| Wheel (n equal segments) | floor(r × n) | 1 ÷ n | 1/n each |
| Wheel (weighted segments) | proportional walk | weight ÷ total | weight / total |
WheelNamer uses browser-native Math.random() (xorshift128+) seeded by OS entropy on every page load. No server-side influence. No patterns. No memory between spins.