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:

  1. 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,
  2. choose a stream cipher—didn't you read above?
  3. 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:

  1. if you do not have hardware support, it might be slow (but be sure it has a real impact on your application);
  2. stream ciphers work in blocks—you might not want to use so much memory;
  3. for the same reason, the time to emit a value might vary significantly, and this might be a problem (e.g., real-time systems);
  4. 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=native makes 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-loops option. 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.

Comparison of PRNGs
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).

Comparison of Floating-Point PRNGs
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):

Comparison of Vectorized PRNGs
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).