Skip to main content
← Gists
typescript Feb 24, 2026

Splitmix32: A Fast Seeded PRNG

A 32-bit seeded pseudorandom number generator derived from MurmurHash3's finalizer. Deterministic, fast, and good enough for simulations that need reproducibility — not cryptography.

Math.random() gives you no control over the sequence it produces — you cannot replay it, checkpoint it, or share it across environments. Simulations, procedural generation, and deterministic tests all need a seeded PRNG: feed in the same seed, get back the same sequence. Splitmix32 fills that role in 13 lines of TypeScript.

The implementation

splitmix32.ts
export function splitmix32(seed: number):
  () => number /* [0, 1) */ {
    let state = seed | 0;
    return () => {
      state |= 0;
      state = state + 0x9e3779b9 | 0;
      let t = state ^ state >>> 16;
      t = Math.imul(t, 0x21f0aaad);
      t = t ^ t >>> 15;
      t = Math.imul(t, 0x735a2d97);
      return ((t = t ^ t >>> 15) >>> 0) / 4294967296;
    };
  }

Each call advances the internal state and returns a float in [0,1)[0, 1). JavaScript numbers are 64-bit floats by default, so the | 0 coercions force 32-bit integer semantics. Without those coercions, additions like state + 0x9e3779b9 would silently overflow into floating-point territory. Math.imul serves the same purpose for multiplication: true 32-bit multiply, no float promotion.

Where it comes from

The SplitMix family was introduced by Steele, Lea, and Flood in Fast Splittable Pseudorandom Number Generators at OOPSLA 2014. That paper defines a 64-bit generator included in Java’s JDK8 (java.util.SplittableRandom). The 32-bit variant here adapts the same structure — a Weyl sequence counter feeding a mixing function — but swaps the 64-bit mixer for one derived from MurmurHash3’s fmix32 finalizer with improved constants.

The Weyl sequence: 0x9e3779b9

The state advances by a fixed increment each call:

weyl-step.ts
a = a + 0x9e3779b9 | 0;

0x9e3779b9 is 232/φ\lfloor 2^{32} / \varphi \rfloor, where φ1.618\varphi \approx 1.618 is the golden ratio. This forms a Weyl sequence: a progression s,  s+γ,  s+2γ,  s, \; s+\gamma, \; s+2\gamma, \; \ldots modulo 2322^{32}. The golden ratio has the slowest-converging continued fraction of any irrational number, which means stepping by this constant spreads values as uniformly as possible across the full 32-bit range. The period is exactly 2322^{32} — every possible state is visited once before the sequence repeats.

The mixing function: 0x21f0aaad and 0x735a2d97

The Weyl sequence alone produces values with obvious structure — consecutive outputs differ by a constant. The mixer destroys that structure through three rounds of xorshift-multiply:

mixer.ts
// Round 1: break up high-bit patterns
let t = a ^ a >>> 16;
t = Math.imul(t, 0x21f0aaad);

// Round 2: scramble remaining correlations
t = t ^ t >>> 15;
t = Math.imul(t, 0x735a2d97);

// Round 3: final diffusion
t = t ^ t >>> 15;

Each xorshift (x ^ x >>> n) copies high bits into lower positions, creating dependencies between bit groups. The subsequent multiply amplifies those dependencies across the full word — flip a single input bit, and roughly half the output bits flip with it. This property is called avalanche, and it is the reason the output passes statistical randomness tests.

In more precise terms, a perfect mixer would flip each output bit with a 50% chance for every flipped input bit. Real mixers deviate from this ideal; the deviation is measured as avalanche bias.

The specific constants 0x21f0aaad and 0x735a2d97 were discovered computationally. Chris Wellons’ hash-prospector project found them through hill climbing and genetic algorithms, optimizing for the lowest possible avalanche bias. These constants achieve a bias of 0.1076\approx 0.1076, slightly beating the original MurmurHash3 fmix32 finalizer (0x85ebca6b / 0xc2b2ae35). The shift widths 1616, 1515, and 1515 were co-optimized alongside the multipliers.

Why it’s a bijection

The mixer is a composition of bijections on 32-bit integers. XOR-right-shift by nn bits is invertible because the top nn bits remain unchanged and can reconstruct the rest. Multiplication by any odd number mod232\bmod 2^{32} is also invertible, since odd numbers are coprime to 2322^{32}. Both 0x21f0aaad and 0x735a2d97 are odd. The consequence: every input maps to a unique output — no collisions, no wasted states.

When not to use it

Splitmix32 has only 32 bits of state, so an attacker who observes a single output can brute-force the seed in under a second. For anything security-sensitive, reach for crypto.getRandomValues(). For simulations, procedural content, and deterministic tests, splitmix32 is more than enough.