When talking about random number generation - for making games fun, not for making SSH keys - the C and C++ standard libraries come up short in many ways. The classic C srand()/rand() is biased, not thread-safe, not portable, and offers limited range. The C++ <random> library is inconvenient, easy to misuse, provides poor guarantees, and is not available at compile time.
The best pseudo-random number generator (PRNG) offered by the C++ standard library is likely the Mersenne Twister. But choosing std::mt19937 over more efficient alternatives like Xorshift or a PCG variant sacrifices a significant amount of performance in addition to the problems listed above.
For a deep, game-focused comparison of 47 PRNGs across 9 platforms, see Rhet Butler’s excellent RNG Battle Royale (2020), which highlights the performance, portability, and statistical quality concerns that matter most in real-world game development. Several of the top-performing generators featured there - including Romu and SmallFast - are included here.
And so; if you're making games and need your random number generator to be:
- small (16 or 32 bytes) and fast
- deterministic across platforms (e.g., portable!)
- easy to seed
- feature-rich (ints, floats, coin flip, ranges, pick-from-collection, etc.)
- executable at compile time
- compatible with all of your favorite STL algorithms and distributions (
std::shuffle,std::sample,std::*_distribution, etc.)
... go ahead and copy-paste any of these engines and the random.hpp interface, and go forth and prosper. Let me know if you find bugs or add any cool new features!
To use a PRNG:
#include "small_fast64.hpp"
#include "random.hpp"
rnd::Random<SmallFast64> rng{1234}; // Seeded generator, powered by the SmallFast64 engine.
int damage = rng.between(10, 20); // Random int in [10, 20)Use Random<E> to access convenient utilities like floats, coin flips, Gaussian samples, picking from containers, color packing, and more.
Want to use your own engine? It only needs to satisfy the RandomBitEngine concept (concepts.hpp).
All the provided engines are very fast:
They are also compact (16 or 32 bytes), produce high-quality randomness, and can even run at compile time. I recommend using the 64-bit output versions unless you have a measured performance reason not to. The 32-bit engines work fine, but their output values are smaller than size_t on most systems. This means they might not handle indexing very large containers (over about 4.2 million elements). Such large containers are rare through and, in debug builds, the Random<E> code will alert you if this problem occurs.
| File Name | Output Width | Description |
|---|---|---|
romuduojr.hpp |
64 bits | C++ port of Mark Overton’s RomuDuoJr. Winner of Rhet Butler’s RNG Battle Royale (2020)! |
konadare192.hpp |
64 bits | C++ port of Pelle Evensen's konadare192px++. Second fastest and second smallest 64-bit PRNG in this lineup! |
pcg32.hpp |
32 bits | C++ port of Melissa O’Neill’s minimal PCG32. Wikipedia: Permuted congruential generator |
xoshiro256ss.hpp |
64 bits | C++ port of David Blackman & Sebastiano Vigna's xoshiro256** 1.0 generator. Wikipedia: Xorshift. |
small_fast32.hpp |
32 bits | C++ port of Bob Jenkins’ 32-bit “Small Fast” PRNG (two-rotate). |
small_fast64.hpp |
64 bits | A 64-bit three-rotate implementation of the above. Three rotates (7, 13, 37) ensure stronger avalanche behavior than a naïve two-rotate 64-bit variant. |
Each engine satisfies the RandomBitEngine concept, which extends the C++20 UniformRandomBitGenerator to ensure compatibility with the STL. You can use the engines directly, but RandomBitEngine provides only a minimal set of features: seeding, advancing, reporting min() and max(), comparison (of state) and generating random unsigned integers.
The engines are kept simple so they can be swapped easily with the Random<E> template. Random<E> wraps any engine - including your own - to provide a consistent, user-friendly interface designed for game development. See the full interface below.
| Method | Description |
|---|---|
Random<E>() |
Default-constructs the engine E with its default seed |
Random<E>(seed) |
Constructs by seeding the engine with seed |
Random<E>(engine) |
Constructs by copying an existing engine instance |
operator==(other) |
Returns true if two generators have identical state |
min() |
Returns the engine’s minimum possible value (typically 0) |
max() |
Returns the engine’s maximum possible value |
next() / operator()() |
Returns the next random number in [min(), max()) |
next(bound) / operator()(bound) |
Random integer in [0, bound), using Lemire’s FastRange (minimal bias, very fast) |
next<N, T>() |
Compile-time bounded integer in [0, N), optionally returned as type T; optimized for power-of-2 bounds1 |
between(lo, hi) |
Random integer or float in [lo, hi) (integer if lo, hi are integral, else float) |
bits(n) |
Runtime: returns the top n random bits (1 ≤ n ≤ digits(result_type)), packed into T (default: result_type)1 |
bits<N, T>() |
Compile-time: returns the top N random bits as type T; constraints checked at compile time1 |
bits_as<T>() |
Convenience: returns an unsigned T filled with digits(T) high-quality random bits |
normalized<F>() |
Returns float F in [0.0, 1.0), using the Inigo Quilez float hack |
signed_norm<F>() |
Returns float F in [-1.0, 1.0) |
coin_flip() |
Fair coin flip (true ~50%) |
coin_flip(p) |
Weighted coin (true with probability p, where p is in [0.0, 1.0]) |
index(range) |
Returns a random index into any sized range |
iterator(range) |
Returns an iterator to a random element |
element(range) |
Returns a reference to a random element |
gaussian(mean, stddev) |
Approximate normal sample via the Irwin–Hall sum-of-12 method |
discard(n) |
Advances the underlying engine by n steps |
seed() |
Reseeds the engine back to its default state |
seed(v) |
Reseeds the engine with value v |
split() |
Produces a decorrelated, forked engine (useful for parallel streams) |
engine() / engine() const |
Access the underlying engine instance (for manual serialization, debugging, etc.) |
These benchmarks use QuickBench to let you compare performance across different use cases:
next()– raw unsigned output: baseline performancenext(bound)– bounded integer using Lemire’s method: efficient rejection-free range generationnormalized<float>()– floating-point in [0.0, 1.0): branchless float generation (IQ trick)
Each benchmark loops over one million values and compares multiple engines side by side, including the std library and cstdlib alternatives.
TL;DR: seeding.hpp is a grab bag of portable, high-entropy seeding strategies - for tools, tests, or procedural generation, at runtime or compile time.
All engines in this library are seeded from a single uint64_t or uint32_t value. They provide a hardcoded default seed, so default construction (Random<E>()) is always valid - but produces the same sequence every time.
To get varied sequences, you’ll want to provide a high-entropy seed. std::random_device is often used for this - it's typically hardware-backed and works fine on most platforms. But it can be slow, unavailable (e.g. on embedded systems, or at compile time), and is unsuitable when you need determinism.
In game development determinism is often needed. Procedural systems such as level generation, loot tables, or AI behavior, all benefit from consistent seeds that allow us to reproduce the same output across runs and platforms. This is where seeding.hpp shines! It provides a variety of seeding techniques that work in both runtime and compile-time contexts, letting you draw entropy from sources like timestamps, thread IDs, game assets, player data, and even compilation metadata.
//Example usage:
using rnd::Random;
using seed::to_32; // Convenience alias for converting a 64-bit seed to 32 bits (for smaller engines).
// Compile-time seeding:
constexpr auto seed1 = seed::from_text("my_game_seed");
constexpr auto seed2 = seed::from_source(); // Different for each compilation unit (source file)
constexpr auto seed3 = SEED_UNIQUE_FROM_SOURCE(); // Different for each macro expansion, even within the same source file
// Runtime seeding:
Random<SmallFast32> rng1(to_32(seed::from_time())); // Wall clock time, folded down to 32 bits for SmallFast32
Random<SmallFast64> rng2(seed::from_system_entropy());// Uses std::random_device (hardware/system entropy)
// Pseudo-entropy sources:
Random<RomuDuoJr> rng3(seed::from_thread()); // Unique per thread
Random<PCG32> rng4(to_32(seed::from_stack())); // Varies per run of the application, if ASLR is active
Random<PCG32> rng5(to_32(seed::from_cpu_time())); // Varies with CPU time consumed by the process; can reflect workload or scheduling
// Maximum entropy:
Random<Xoshiro256SS> rng6(seed::from_all()); // Combines all sources (time, thread, stack, entropy, etc.)These utilities help you ensure that your random number generators are seeded appropriately - whether you need reproducibility, speed, or maximal entropy.
This repository is primarily licensed under the MIT License. See LICENSE for full details.
This project includes, or is based on, the following PRNG engines and reference implementations:
- RomuDuoJr: Based on Rhet Butler’s C++ wrapper (public domain), itself inspired by Mark Overton’s Romu family.
- SmallFast32 / SmallFast64: Based on Bob Jenkins’ reference implementation (public domain).
- xoshiro256**: Based on David Blackman & Sebastiano Vigna’s reference code (public domain).
- splitmix64: By Sebastiano Vigna (public domain).
- PCG32: Based on M.E. O’Neill’s reference implementation (Apache License 2.0).
- konadare192px++: By Pelle Evensen (Apache License 2.0).
- moremur: By Pelle Evensen (public domain).
Where applicable, copyright and license information is included in the header of each source file.
All additional code, wrappers, and modifications © Ulf Benjaminsson, licensed under the MIT License unless otherwise noted.
