Quant Memo
Core

Pseudorandom Number Generators

Computers can't generate true randomness, so simulations rely on deterministic algorithms that produce sequences statistically indistinguishable from random — and the choice of generator and seed quietly shapes every Monte Carlo result built on top of it.

A Monte Carlo option pricer simulates a million paths of a stock's price and wants each path to be "random" — but a computer is a deterministic machine, and asking it for true randomness (without special hardware) is asking for something it structurally can't do. Instead, every simulation library uses a pseudorandom number generator: a deterministic algorithm producing a long sequence of numbers that pass statistical tests for randomness, even though the entire sequence is completely determined by one starting value.

A PRNG takes a seed — a starting number — and applies a deterministic formula to produce a long sequence that looks statistically random: no obvious patterns, uniform distribution, low correlation between successive values. The same seed always reproduces the exact same sequence, which is a feature, not a flaw — it's what makes a Monte Carlo simulation debuggable and reproducible.

Why "pseudo"

Early generators like the linear congruential generator computed each number from the last via xn+1=(axn+c)modmx_{n+1} = (a \cdot x_n + c) \bmod m for fixed constants a,c,ma, c, m — simple, fast, but with a short repeating cycle and detectable correlations between consecutive draws, which showed up as visible lattice patterns in high dimensions. Modern default generators — the Mersenne Twister (NumPy's historic default) and PCG64 (NumPy's current default via np.random.default_rng()) — use more elaborate internal state and mixing to push the repeat cycle out to an astronomically large length and pass far stricter statistical test suites.

None of them are "truly" random: given the internal state, the entire future sequence is fixed. That's why PRNGs are unsuitable for cryptography (an attacker who recovers the state can predict every future draw) but perfectly fine, and in fact desirable, for simulation, where reproducibility matters more than unpredictability.

Convergence explorer
true meansamples →
after n = 200estimate -0.025error 0.025std error 0.071

Run the simulation above and watch a running average settle toward the true value as more draws accumulate — every value it's built from came from a deterministic PRNG sequence, yet in aggregate the draws behave exactly like independent random samples for statistical purposes.

Worked example

rng = np.random.default_rng(seed=42) fixes the starting state. rng.normal(0, 1, 5) might produce [0.305, -1.040, 0.751, 0.941, -1.951]. Run that exact code again in a fresh session, same seed: identical five numbers, every time, on any machine. Now run rng.normal(0, 1, 5) a second time in the same session without resetting the seed — it continues the sequence rather than repeating, producing five different numbers, because the generator's internal state advanced after the first call. This distinction — same seed reproduces from scratch, but a live generator object never repeats mid-stream — trips up anyone who expects "seeded" to mean "always the same numbers no matter when you ask."

What this means in practice

Reproducibility is why every serious backtest or Monte Carlo pricer fixes and logs its seed: a bug report that says "the simulation gave a weird result" is only debuggable if the exact random sequence that produced it can be regenerated. Comparing two strategy variants fairly often means using the same seed for both, so any difference in results comes from the strategy logic, not from the two runs happening to draw different random paths.

Creating a fresh, differently-seeded (or unseeded, time-seeded) generator inside a loop — instead of drawing repeatedly from one generator object created outside the loop — is a common bug: unseeded generators seeded by system time can produce correlated or even identical seeds if called in rapid succession, silently breaking the independence a simulation assumes between iterations.

Related concepts

Practice in interviews

Further reading

  • Matsumoto & Nishimura, 'Mersenne Twister' (1998)
  • NumPy documentation, 'Random sampling' (`Generator`, `PCG64`)
ShareTwitterLinkedIn