Newsroom
Audit8 min read

We tried to show the South African PowerBall is rigged. It isn't.

By Dylan Wolpe

The short version

  • Across 1,558 South African PowerBall draws in the current 5/50 format, real results compress to 31.317 bits per draw against 31.253 for a synthetic fair generator — a difference of z = +0.97, which is indistinguishable from fair.
  • Ball frequencies are uniform (main balls chi-squared 37.7 on 49 degrees of freedom), and shuffling the draw order changes the compressed size by 17 bytes in 6,099, so there is no serial structure either.
  • Our first pass reported a huge apparent bias. It was an artefact of pooling two different game formats: balls 46 to 50 did not exist before November 2015, so across the pooled history they look drastically under-drawn.
  • A compressor makes a usable structure detector only when it is run against a control encoded identically. Without the control, the method reliably finds structure that is an artefact of the encoding.

A compressor is a structure detector wearing a different hat. It makes files smaller by finding patterns and describing them more briefly, which means the size of the output is a measurement: how much structure is in here? Point that instrument at something that is supposed to have no structure at all, and you have a randomness audit.

A national lottery is the ideal target. It is meant to be perfectly random, the results are public, and if it were not random, that would matter to several million people. So we pointed the detector at the South African PowerBall.

The answer is that it is fair. Getting to that answer is the interesting part.

The method

Every draw is five main balls from fifty, plus one PowerBall from twenty. If the machine is fair, the information content of a draw is fixed by combinatorics alone:

information floor = log2(C(50,5)) + log2(20) = 25.337 bits/draw
The theoretical floor. No encoder can beat this on fair draws, and one that appears to has found structure that shouldn't exist.

We encode the draw history, compress it with four independent algorithms, and take the best result. Then — and this is the step that makes it an experiment rather than a number — we generate synthetic draws from a known-fair random generator, encode them through exactly the same pipeline, and compress those too.

The comparison is real against synthetic-fair, never real against the theoretical floor. Real draws will always beat the floor, because sorted balls are compressible and the sorting carries no information about fairness. The control absorbs every artefact of the encoding, so whatever difference survives is a property of the lottery rather than of our file format.

The result

parsed 2829 draws; using 1558 in the clean 2019+ 5/50 + 1/20 era

REAL   : raw 9348B -> best-compressed 6099B = 31.317 bits/draw
         {xz: 6640, bz2: 6099, zlib: 6622, zstd: 6642}
SYNTH  : fair-RNG compressed = 31.253 +/- 0.066 bits/draw (n=40 sims)
  => REAL vs fair-RNG z = +0.97   (|z| < ~3 => indistinguishable from fair)

main balls  chi^2=37.7 (df=49) -> normalized z=-1.17
powerball   chi^2=19.3 (df=19) -> normalized z=+0.15
  most-drawn main ball: 29 (183x)   least: 26 (135x)

serial test: ordered=6099B  shuffled=6082B  delta=+17B  (no serial structure)
Output of benchmarks/experiments/lottery/sa_powerball_probe.py against downloaded draw history. Forty synthetic runs for the control.

Real draws land 0.97 standard deviations from the synthetic-fair mean. That is not a small effect; it is no effect. Ball frequencies are uniform on a straight chi-squared test as well, and the gap between the most-drawn ball (29, at 183 appearances) and the least (26, at 135) is exactly the spread you should expect from 1,558 fair draws — over enough trials, perfectly fair dice still produce a leaderboard.

The serial test asks a different question: is there structure in the orderof draws, the thing every “hot numbers” system implicitly assumes? We shuffle the draws and recompress. If sequence carried information, destroying it would cost bytes. It changed the total by 17 bytes out of 6,099 — noise.

The same detector, pointed at a physically biased roulette wheel, registers −753 sigma. Here it registers +0.97. An instrument worth trusting has to be able to say “nothing here”.

The mistake we made first

Our first run found a dramatic bias. Certain balls appeared to be drawn far less often than chance allowed, at a confidence level that would ordinarily mean something was badly wrong with the machine.

It was entirely our fault, and the cause is worth the whole article.

The South African PowerBall has not always been the same game. It ran as 5 balls from 45 until November 2015, then changed to 5 from 50. We had pooled the full downloaded history and treated it as one dataset. Balls 46 through 50 were therefore impossible for the first several years of the record — not unlucky, not under-drawn, simply not in the machine. Averaged across the pooled set they looked massively deficient, and the detector faithfully reported exactly what it saw.

EraFormatBalls 46–50
Pre-Nov 20155 from 45Cannot be drawn
Nov 2015 onward5 from 50Drawn normally
Pooled (our error)Treated as oneAppear heavily biased

The detector was not wrong. There genuinely is structure in the pooled file. The structure is a rule change in the game, and we had asked a question that could not tell the difference between “this lottery is rigged” and “this lottery changed its rules”.

The fix is the line in the output that says using 1558 in the clean 2019+ 5/50 + 1/20 era. Restrict to a single stable format, and the signal vanishes — because it was never a signal.

Why we are publishing the error

Because it is the most transferable thing here. This failure mode is not specific to lotteries; it is what happens whenever a sensitive detector meets a heterogeneous dataset. A schema migration, a firmware update, a sensor swap, a change in how a vendor formats their export — all of these look exactly like a discovery. The more sensitive your instrument, the more reliably it will find them and hand you a very confident wrong answer.

We have the same class of detector pointed at industrial and clinical data, where the finding is not a fun fact about a lottery but a maintenance decision. So the discipline that caught this one is load-bearing: always compare against a control that went through the identical pipeline, and when a result looks spectacular, suspect the pipeline before the world.

Reproducing it

The probe is benchmarks/experiments/lottery/sa_powerball_probe.py. It parses saved history pages, makes no network calls, and prints the block above. The draw results are public. If you disagree with the era filter, change it and see what happens — you will rediscover our false positive, which is the fastest way to understand why the control matters.

Related

More from the newsroom