Introduction
This page describes some new pseudorandom number generators (PRNGs) I have been working on recently, in part with David Blackman, and a shootout comparing them with other generators. Details about the generators can be found in this paper (for scrambled linear generators) or in this paper (for scrambled multiply-with-carry generators).
The generators come in two families: scrambled linear generators
(xoshiro/xoroshiro), based on linear recurrences over
F2, and scrambled multiply-with-carry generators, based
on congruential arithmetic. In both cases a fast engine is combined with a
scrambler that removes its weaknesses, and for each kind of use we suggest one
generator from each family. Scrambled linear generators use only very simple
operations, which makes them suitable also for hardware implementations;
multiply-with-carry generators need a 64×64→128-bit multiplication, but
on x86-64 CPUs they can be even faster.
Information about my previous xorshift-based generators
can be found here, but they have been
entirely superseded by the new ones, which are faster and
better. As part of our study, we developed a very strong test for
Hamming-weight dependencies
that gave a number of surprising results.
How to Choose a PRNG
If you have to choose a PRNG for your application, you should, in this order:
- choose a stream cipher, like ChaCha20 or AES in counter mode; this is, for example, the choice of Rust, arguably one of the technically more advanced languages; otherwise,
- choose a stream cipher—didn't you read above?
- in case you cannot use a stream cipher, find the fastest non-cryptographic PRNG of good statistical quality that satisfies your needs.
There might be a few reasons for which you might not want to use a stream cipher:
- if you do not have hardware support, it might be slow (but be sure it has a real impact on your application);
- stream ciphers work in blocks—you might not want to use so much memory;
- for the same reason, the time to emit a value might vary significantly, and this might be a problem (e.g., real-time systems);
- you might need a generator you can easily embed in a hardware design;
… and so on. Indeed, for these cases Rust provides a SmallRng
which is our xoshiro256++.
64-bit Generators
xoshiro256++ and MWC192X are our 64-bit
all-purpose generators (not cryptographically secure generators, though, like
all PRNGs in these pages). They have excellent (sub-ns) speed, a state space (256
and 192 bits, respectively) that is large enough for any parallel application, and
they pass all tests we are aware of.
xoshiro256++ is a scrambled linear generator (XOR/shift/rotate): it uses
only very simple operations, which makes it suitable also for hardware
implementations, and it can be easily vectorized.
MWC192X is a scrambled multiply-with-carry generator: it needs a full
64×64→128-bit multiplication, but on x86-64 CPUs it is even faster (see
the shootout), and its underlying generator is a
multiplicative congruential generator with prime modulus, rather than a linear
recurrence over F2. The final X in the name is our scrambler X:
each step computes the 128-bit value t = ax + c from
which the new digit and the new carry are derived, and X returns the new digit
xored with the upper 64 bits of t. In a multiply-with-carry generator these
bits are the new carry, so X reduces to the scrambler of David Thomas's MWC64X
generator.
See the papers on scrambled linear generators and on scrambled multiply-with-carry generators for details.
32-bit Generators
xoshiro128++ and MWC128X/32 are our 32-bit
all-purpose generators. They are the 32-bit counterparts of
xoshiro256++ and MWC192X, so similar comments apply:
xoshiro128++ uses only 32-bit operations, whereas the only 64-bit
operations of MWC128X/32 are a multiplication and a sum. Their state
is too small for large-scale parallelism: their intended usage is inside embedded
hardware or GPUs. We do not believe at this point in time a 32-bit generator with
a larger state can be of any use.
Small-State Generators
If you are tight on space, xoroshiro128++
(XOR/rotate/shift/rotate) uses half of the space of xoshiro256++, and
MWC128X two thirds of the space of
MWC192X, but it is slower, as each multiplication depends on the result
of the previous one. These generators are suitable only for low-scale parallel
applications.
For 32-bit outputs, xoroshiro64** and MWC64X/32 have a state of just 64 bits.
MWC64X/32 is David Thomas's MWC64X with our multiplier, and it is ideal
when registers are scarce, as on GPUs. Their state space is not large enough for
any parallel application.
Floating-Point Generators
If one has to generate only 64-bit floating-point numbers (by
extracting the upper 53 bits), xoshiro256+
and, if you are tight on space, xoroshiro128+
are generators with statistical properties analogous to those of
xoshiro256++ and xoroshiro128++, but with a cheaper
scrambler. For general
usage, one has to consider that their lowest bits have low linear complexity and
will fail linearity tests; however, low linear
complexity of the lowest bits can have hardly any impact in practice, and certainly
has no impact at all if you generate floating-point numbers using the upper bits
(we computed a precise estimate of the linear complexity of
the lowest bits). Moreover, xoroshiro128+ exhibits a mild dependency in
Hamming weights that generates a failure after 5 TB of output in our test. We believe this slight bias cannot affect any
application.
Additional Generators
Scrambled linear generators
xoshiro256**, xoroshiro128**, and xoshiro128** use a different
scrambler, based on multiplications: with respect to
xoshiro256++, xoroshiro128++, and
xoshiro128++, they are fully equidistributed (e.g., xoshiro256** is 4-dimensionally equidistributed,
whereas xoshiro256++ is just 3-dimensionally equidistributed). See the paper for a
detailed discussion of their differences.
If for any reason (which reason?) you need more state, we provide
in the same vein xoshiro512++ / xoshiro512** / xoshiro512+ and
xoroshiro1024++ / xoroshiro1024** / xoroshiro1024*.
For 32-bit floating-point numbers, xoshiro128+
and, if you are tight on space, xoroshiro64*
are the analogues of xoshiro256+ and xoroshiro128+. All 32-bit generators
pass all tests we are aware of, with the exception of linearity tests (binary
rank and linear complexity) for xoshiro128+ and
xoroshiro64*: in this case, due to the smaller number of output bits,
the low linear complexity of the lowest bits is easily detected. Analogously to the 64-bit case, generating
32-bit floating-point numbers using the upper bits will not use any of the bits
with low linear complexity.
We do not suggest any particular 16-bit generator, but it is
possible to design relatively good ones using our techniques. For example,
Parallax has embedded in their Propeller
2 microcontroller multiple 16-bit xoroshiro32++ generators.
Scrambled multiply-with-carry generators
MWC256X has the same speed as
MWC192X and a larger state.
If the quality of the underlying lattice is more important than
speed, GMWC128X/GMWC256X are based on the generalized
multiply-with-carry generators of Goresky and Klapper, whose more general
modulus avoids (GMWC128X) or considerably weakens
(GMWC256X) the defect of multiply-with-carry generators. They need two
additional multiplications on the dependency chain of the carry, so they are about
four times slower than MWC192X.
Other generators
If you need Pierre L'Ecuyer's MRG32k3a,
which is widely used in simulation software, we provide a fast, branchless implementation in C
and Java based on 64-bit integers, which generates the same stream as the original
one. The standard implementation contains three tests: one corrects the combined
output, and two correct negative modular residuals. We avoid the first by
arithmetization, and the other two by keeping the argument of the modulo operator
nonnegative, which is possible because of the small size of the numbers involved.
Moreover, the output is computed on the current state, so the processor can compute
it in parallel with the next state. On our benchmark hardware the result is 2.4 (Clang) to 5.6
(GCC) times faster than the original double-based implementation, and twice as fast
as a straightforward 64-bit integer implementation compiled with GCC (Clang turns
the tests of the latter into conditional moves, making it almost as fast).
Jumping and Seeding
All generators provide jump functions that make it possible to simulate any number of calls to the next-state function in constant time. We provide ready-made jump functions for a number of calls equal to the square root of the period, to make it easy generating non-overlapping sequences for parallel computations, and equal to the cube of the fourth root of the period, to make it possible to generate independent sequences on different parallel processors.
For scrambled linear generators, which are based on linear recurrences, a jump
needs a suitable jump polynomial; we also provide arbitrary jumps by computing such polynomials on the fly. Multiply-with-carry
generators simulate a multiplicative congruential generator with prime modulus, so
a jump is a modular multiplication of the state, which uses a minimal multiprecision library (MWC64X/32, whose
modulus fits in 64 bits, uses 128-bit arithmetic instead); the code provides also
jumps of arbitrary length.
We suggest to use SplitMix to initialize
the state of our generators starting from a 64-bit seed. For multiply-with-carry generators, we suggest to
set the carry to one (for GMWC generators, to twice the constant
GMWC_MINUSA0) and to fill the digits using
SplitMix.
Adoption
These are some of the languages/environments that have adopted our generators, in no particular order.
JavaScript
xorshift128+ is presently used in the JavaScript
engines of
Chrome,
Node.js,
Firefox,
Safari and
Microsoft
Edge (the link refers to the legacy, ChakraCore-based Edge; today's
Chromium-based Edge uses xorshift128+ through V8, like Chrome).
Rust
The SmallRng from
the rand
crate is xoshiro256++ or xoshiro128++, depending
on the platform.
java.util.random
I worked with Guy Steele on the new
family of PRNGs available in Java 17. The family, called LXM, uses new, better
tables of multipliers for LCGs with power-of-two moduli. Moreover,
java.util.random contains ready-to-use implementations of
xoroshiro128++ and xoshiro256++.
.NET
In version 6, Microsoft's .NET framework has
adopted
xoshiro256** and xoshiro128** as default PRNGs.
Erlang
The parallel functional language Erlang implements several
variants of xorshift/xoroshiro-based generators adapted in
collaboration with Raimo Niskanen for Erlang's
58/59-bit arithmetic.
GNU FORTRAN
GNU's implementation of the FORTRAN language uses
xoshiro256** as default PRNG.
Julia
The Julia programming language uses
xoshiro256++ as default PRNG.
Lua
The scripting language Lua uses xoshiro256** as default PRNG.
Folly
The open-source library Folly from Facebook
provides
an implementation of xoshiro256++.
JPEG XL
The JPEG XL standard uses xorshift128+
to generate white noise images.
IoT
The IoT operating system Mbed uses
xoroshiro128+ as default PRNG, and Zephyr provides
xoshiro128++ (which replaced
xoroshiro128+).
gretl
The Gnu Regression, Econometrics and Time-series Library
uses xoshiro256+ as PRNG.
Randompack
Randompack
uses xoshiro256++ as default PRNG.
A PRNG Shootout
I provide here a shootout of a few recent 64-bit PRNGs that are quite widely used. The purpose is that of providing a consistent, reproducible assessment of two properties of the generators: speed and quality. The code used to perform the speed tests is available for download.
Speed
The speed reported in this page is the time required to emit 64
random bits, and the corresponding number of clock cycles required to generate a
byte. If a generator is 32-bit in nature, I glue two consecutive outputs. Note
that I do not report results using GPUs or explicit SIMD instructions, with an
exception for the very common SFMT and dSFMT: for that to be meaningful, I should have
implementations for all generators. Otherwise, with suitable hardware support I
could just use AES in counter mode and get 64 secure bits in about 0.6 ns
(or just use Randen). The tests were
performed on an AMD EPYC™ 9R45 CPU (Zen 5) on AWS, without simultaneous
multithreading, with the frequency fixed at the maximum (4.5 GHz) and
every benchmark running alone on one core, using gcc 16.2.0 and clang 23.1.3. The value
reported is the minimum over five runs of 3 × 109
outputs.
A few caveats:
- There is some looping overhead, but subtracting it from the timings is not going to be particularly meaningful due to instruction rescheduling, etc.
- Relative speed might be different on different CPUs and in different scenarios.
- Code has been compiled with
-O3 -march=native. The option-march=nativemakes it possible to use all instructions available on the CPU, but it may also make the compiler vectorize the benchmark loop, which is not possible when the generator is embedded in user code: this happens with SplitMix, whose state advances by a constant, so for it I report the timing with auto-vectorization disabled. - Code has been compiled using the
-fno-unroll-loopsoption. This option is essential to get a sensible result: without it, the compiler may perform different loop unrolling depending on the generator. If you find timings that are significantly better than those shown here on comparable hardware, they are likely to be due to compiler artifacts (e.g., vectorization). - The benchmark loop accumulates the outputs, to prevent the compiler from removing the calls to the generator. The accumulation uses a xor, except for generators whose output is computed with a final xor (e.g., multiply-with-carry generators with the scrambler X), for which it uses a sum: otherwise, the compiler could merge the two xors and skip part of the computation.
- Timings are taken running a generator for billions of times in a loop; but this is not the way you use generators. Register allocation might be very different when the generator is embedded in an application, leading to constants being reloaded or part of the state space being written to main memory at each iteration. These costs do not appear in the benchmarks below.
To ease replicability, I distribute a harness performing the
measurement. You just
have to define a next() function and
include the harness. But the only realistic
suggestion is to try different generators in your application and see what happens.
Quality
This is probably the more elusive property
of a PRNG. Statistical failures were determined using the linear-complexity
test of TestU01
(Pierre L'Ecuyer and Richard Simard, “TestU01: A C library for empirical
testing of random number generators”, ACM Trans. Math. Softw. 33(4),
Article 22, 2007), the binary-rank test of PractRand, our test for Hamming-weight dependencies, described in our paper, the
collision test of coll-birth,
and the modular rank and linear-complexity tests of modlin.
As I already remarked, our only
generator failing the test for Hamming-weight dependencies (but only after
5 TB of output) is xoroshiro128+. 64-bit generators using the
+ scrambler, which are meant for floating-point generation, are tested
only on their upper 53 bits.
I report the period of each generator and its footprint in bits: a generator gives “bang-for-the-buck” if the base-2 logarithm of the period is close to the footprint. Note that the footprint can be significantly larger than expected because of cyclic access indices and of state bits that do not contribute to the period.
| PRNG | Footprint (bits) | Period | Statistical failures | ns/64 bits (GCC) | cycles/B (GCC) | ns/64 bits (Clang) | cycles/B (Clang) |
|---|---|---|---|---|---|---|---|
xoshiro256++
| 256 | 2256 − 1 | — | 0.58 | 0.33 | 0.58 | 0.33 |
| MWC192X | 192 | ≈2191 | — | 0.52 | 0.30 | 0.51 | 0.29 |
xoroshiro128++
| 128 | 2128 − 1 | — | 0.66 | 0.38 | 0.69 | 0.39 |
| MWC128X | 128 | ≈2127 | — | 0.94 | 0.53 | 0.95 | 0.54 |
xoshiro256+
| 256 | 2256 − 1 | — | 0.52 | 0.30 | 0.54 | 0.31 |
xoroshiro128+
| 128 | 2128 − 1 | Hamming-weight dependency (5 TB) | 0.66 | 0.37 | 0.66 | 0.38 |
| GMWC128X | 128 | ≈2127 | — | 2.03 | 1.15 | 2.04 | 1.16 |
| GMWC256X | 256 | ≈2255 | — | 2.02 | 1.15 | 2.04 | 1.16 |
| SFC64 | 256 | ≥264 | — | 0.49 | 0.28 | 0.66 | 0.38 |
| SplitMix | 64 | 264 | Collision | 0.62 | 0.35 | 0.60 | 0.34 |
| PCG64-DXSM (NumPy) | 256 | 2128 | — | 1.17 | 0.67 | 1.01 | 0.57 |
| PCG (Go) | 128 | 2128 | — | 1.43 | 0.81 | 1.42 | 0.81 |
Ran
| 192 | ≈2191 | — | 1.36 | 0.77 | 1.37 | 0.78 |
MT19937-64
(Mersenne Twister)
| 20000 | 219937 − 1 | Binary rank, linear complexity | 1.46 | 0.83 | 1.14 | 0.65 |
SFMT19937
(uses SSE2 instructions)
| 20000 | 219937 − 1 | Binary rank, linear complexity | 0.99 | 0.56 | 1.22 | 0.69 |
SFMT607
(uses SSE2 instructions)
| 672 | 2607 − 1 | Binary rank, linear complexity, Hamming-weight dependency (400 MB) | 1.16 | 0.66 | 0.97 | 0.55 |
WELL512a
| 544 | 2512 − 1 | Binary rank, linear complexity, Hamming-weight dependency (3 PB) | 3.58 | 2.03 | 3.57 | 2.03 |
| MIXMAX (N = 17, returns only 61 significant bits) | 1184 | ≈2976 | Modular rank, modular linear complexity | 1.08 | 0.61 | 1.32 | 0.75 |
The following table compares instead two ways of generating floating-point numbers, namely
the 521-bit dSFMT,
which
generates directly floating-point numbers with 52 significant bits, and
xoshiro256+ followed by a standard conversion of
its upper bits to a floating-point number with 53 significant bits (see below).
| PRNG | Footprint (bits) | Period | Statistical failures | ns/64 bits (GCC) | cycles/B (GCC) | ns/64 bits (Clang) | cycles/B (Clang) |
|---|---|---|---|---|---|---|---|
xoshiro256+ (returns 53
significant bits)
| 256 | 2256 − 1 | — | 0.67 | 0.38 | 0.70 | 0.40 |
dSFMT
(uses SSE2 instructions, returns only 52 significant bits)
| 672 | 2521 − 1 | Binary rank, linear complexity, Hamming-weight dependency (6 TB) | 1.29 | 0.73 | 1.05 | 0.60 |
On our benchmark hardware xoshiro256+ is faster than the dSFMT, even when the
latter fills an array; moreover,
xoshiro256+ has a doubled range of output values, does not need any extra SSE instruction (can be programmed in Java, etc.),
has a much smaller footprint, and its upper bits do not fail any test.
Remarks
Vectorization
Some of the generators can be very easily vectorized, so that multiple instances can be run
in parallel to provide
fast bulk generation. Thanks to an interesting discussion with the Julia
developers,
I've become aware that AVX2/AVX-512 vectorizations of multiple instances of generators using the
+/++ scrambler are impressively fast (links
below point at a speed test to be used with the harness, and the
result will be multiplied by 1024):
| PRNG | ns/64 bits (GCC) | cycles/B (GCC) | ns/64 bits (Clang) | cycles/B (Clang) |
|---|---|---|---|---|
xoshiro256++ (16
parallel instances)
| 0.090 | 0.051 | 0.091 | 0.052 |
xoroshiro128++ (16
parallel instances)
| 0.101 | 0.057 | 0.100 | 0.057 |
xoshiro256+ (16
parallel instances)
| 0.085 | 0.048 | 0.081 | 0.046 |
xoroshiro128+ (16
parallel instances)
| 0.096 | 0.054 | 0.086 | 0.049 |
Note that sometimes convincing the compiler to vectorize is a
slightly quirky process: for example, with gcc (as of version 16) I have to use
-O3 -fdisable-tree-cunrolli -march=native
to vectorize these generators
(-O3 alone will not vectorize, unless there are at least 32 instances;
thanks to Chris Elrod for pointing me at -fdisable-tree-cunrolli). The
best number of instances depends on the compiler, too: with gcc, 32
instances are even faster (0.056–0.070 ns per 64 bits), but with
clang they are several times slower.
A long period does not imply high quality
This is a common misconception. The generator x++ has
period 2k, for any k ≥ 0, provided that x is
represented using k bits: nonetheless, it is a horrible generator.
The generator returning k − 1 zeroes followed by a one has period
k.
It is however important that the period is long enough. A first heuristic rule of thumb is that if you need to use t values, you need a generator with period at least t². Moreover, if you run n independent computations starting at random seeds, the sequences used by each computation should not overlap. Lack of overlapping does not guarantee lack of correlation, but overlapping sequences are certainly correlated.
Now, given a generator with period P, the probability that n subsequences of length L starting at random points in the state space overlap is bounded by n²L/P. If your generator has period 2256 and you run on 264 cores (you will never have them) a computation using 264 pseudorandom numbers (you will never have the time) the probability of overlap would be at most 1/264.
In other words: any generator with a period beyond 2256 has a period that is sufficient for every imaginable application. Unless there are other motivations (e.g., provably increased quality), a generator with a larger period is only a waste of memory (as it needs a larger state), of cache lines, and of precious high-entropy random bits for seeding (unless you're using small seeds, but then it's not clear why you would want a very long period in the first place—the computation above is valid only if you seed all bits of the state with independent, uniformly distributed random bits).
In case the generator provides a jump function that lets you skip through chunks of the output in constant time, even a period of 2128 can be sufficient, as it provides 264 non-overlapping sequences of length 264.
Equidistribution
Every 64-bit scrambled linear generator of ours with n bits of state scrambled
with * or ** is n/64-dimensionally
equidistributed: every n/64-tuple of consecutive 64-bit
values appears exactly once in the output, except for the zero tuple
(and this is the largest possible dimension). Generators based on the
+ or ++ scramblers are however only (n/64 −
1)-dimensionally equidistributed: every (n/64 −
1)-tuple of consecutive 64-bit values appears exactly 264
times in the output, except for a missing zero tuple. The same considerations
apply to 32-bit generators.
Generating uniform doubles in the unit interval
A standard double (64-bit) floating-point number in IEEE floating-point format has 52 bits of significand, plus an implicit bit at the left of the significand. Thus, the representation can actually store numbers with 53 significant binary digits.
Because of this fact, in C99 a 64-bit unsigned integer x should be converted to
a 64-bit double
using the expression
#include <stdint.h>
(x >> 11) * 0x1.0p-53
In Java you can use almost the same expression for a (signed) 64-bit integer:
(x >>> 11) * 0x1.0p-53
This conversion guarantees that all dyadic rationals of the form k /
253
will be equally likely. Note that this conversion prefers the high bits of x
(usually, a good idea), but you can alternatively
use the lowest bits.
An alternative, multiplication-free conversion is
#include <stdint.h>
static inline double to_double(uint64_t x) {
const union { uint64_t i; double d; } u = { .i = UINT64_C(0x3FF) << 52 | x >> 12 };
return u.d - 1.0;
}
The code above cooks up by bit manipulation
a real number in the interval [1 . . 2), and then subtracts
one to obtain a real number in the interval [0 . . 1). If x is chosen uniformly
among 64-bit integers,
the result is chosen uniformly among dyadic rationals of the form k /
252. This
is the same technique used by generators providing directly doubles, such as the
dSFMT.
This technique is supposed to be fast, but on recent hardware it does not seem to give a significant advantage. More importantly, you will be generating half the values you could actually generate. The same problem plagues the dSFMT. All doubles generated will have the lowest significand bit set to zero (I must thank Raimo Niskanen from the Erlang team for making me notice this—a previous version of this site did not mention this issue).
In Java you can obtain an analogous result using suitable static methods:
Double.longBitsToDouble(0x3FFL << 52 | x >>> 12) - 1.0
To adhere to the principle of least surprise, my implementations now use the multiplicative version, everywhere.
Interestingly, these are not the only notions of “uniformity” you can come up with. There are many other possible definitions, but in the end most of them boil down to generating (almost) all possible floating-point numbers with probability proportional to their ULP. This idea can be formalized in many ways, so I published a Rust crate comparing the main existing techniques.
Note that taking a 64-bit random number and dividing it by 264 will not generate floats uniformly because the IEEE floating-point format uses round-to-even (correctly, as it minimizes the propagation of errors).