The xoshiroLibrary

Introduction

David Blackman and Sebastiano Vigna introduced the xoshiro/xoroshiro class of pseudorandom number generators, which are very efficient and, though they have relatively small states, display excellent statistical properties. The mathematical details are to be found in their paper.

xoshiro.h is a single header file, C++ implementation of the complete family of these generators.

The implementations satisfy C++’s std::uniform_random_bit_generator concept and can drive any of the distributions defined in the standard library.

We also provide efficient jump-ahead methods for arbitrary jump sizes, making these generators particularly suitable for parallel processing applications. They are excellent replacements for the “standard” Mersenne Twister generator.

While the implementation is very general, there are simple type aliases for specific preferred instantiations that are known to work well in most situations.

Example

Here is a simple example where we use the default xoshiro generator xso::rng to sample a normal distribution from the standard library.

This is identical to the code shown on the std::normal_distribution webpage except that we use xso::rng as a drop-in replacement for the std::mersenne_twister_engine.

1#include <xoshiro.h>
#include <random>
#include <map>
#include <iomanip>

int main()
{
2    xso::rng gen;

    // lambda: draws a sample from a normal distribution and rounds it to an integer.
    std::normal_distribution d{5.0, 2.0};
    auto random_int = [&d, &gen] { return int(std::round(dist(gen))); };

    // Run many trials and create a histogram of the results in integer buckets.
    std::map<int, int> hist{};
    for (int n = 0; n != 25'000; ++n) ++hist[random_int()];

    // Print that histogram.
    for (auto [bucket, count] : hist) {
        auto n = static_cast<std::size_t>(count / 100);
        std::cout << std::setw(2) << bucket << ' ' << std::string(n, '*') << '\n';
    }
}
1
Everything is in the single xoshiro.h header file. The classes, functions, etc., are all in the xso namespace.
2
This type alias for xso::rng64 produces 64-bit outputs from a specific xoshiro generator with 256 bits of state.

Output:

-3
-2
-1
 0 **
 1 *******
 2 ****************
 3 *****************************
 4 ********************************************
 5 ************************************************
 6 ********************************************
 7 *****************************
 8 ****************
 9 *******
10 **
11
12
14

Installation

This library is header-only, so there is nothing to compile & link — drop the xoshiro.h file somewhere convenient, and you are good to go.

Alternatively, if you are using CMake, you can use the standard FetchContent module by adding a few lines to your project’s CMakeLists.txt file:

include(FetchContent)
FetchContent_Declare(xoshiro URL https://github.com/nessan/bit/releases/download/current/xoshiro.zip)
FetchContent_MakeAvailable(xoshiro)

This command downloads and unpacks an archive of the current version of xoshiro to your project’s build folder. You can then add a dependency on xoshiro::xoshiro, a CMake alias for xoshiro. FetchContent will automatically ensure the build system knows where to find the downloaded header files and any needed compiler flags.

Used like this, FetchContent will only download a minimal library version without any redundant test code, sample programs, documentation files, etc.

The shown URL gets the current version of the library — whatever is in the main branch. For a fixed, stable library version (say release 2.0.0), use a URL parameter like: https://github.com/nessan/xoshiro/releases/download/2.0.0/xoshiro.zip.

Implementation

C versions of the generators are available on the xoshiro/xoroshiro website. Wrapping those routines to conform to the std::uniform_random_bit_generator concept is trivial.

Our implementation in xoshiro.h is distinguished in several other ways:

Generality

Using xoshiro.h you can create any member of the xoshiro/xoroshiro family.

We have State classes that are templatized across the number of state bits, and the parameters (labelled A, B, and C in the literature) that determine how the state is advanced from step to step.

Scrambler classes are templatized across the other parameters (labelled R, S, and T in the literature) that determine how the higher dimensional state is scrambled/reduced to single 32-bit or 64-bit output words.

This means you can instantiate any generator in the xoshiro/xoroshiro family.

For the reasonable optimization levels you are likely to employ in any numerical code, the C++ versions perform identically to the simpler-looking C versions linked above.

Simplicity

While you can instantiate any generator in the xoshiro/xoroshiro family, we also recognize that only a limited number of those generators have been vetted for suitability as being “good”.

Therefore, we provide recommended type aliased default generators you should use in most cases.

Arbitrary Jumps

We provide methods to advance a generator by arbitrary and potentially huge numbers of steps. This contrasts with the C versions, which only define a limited number of jump possibilities.

Huge jumps are used to partition a single random number stream into non-overlapping sub-streams. In parallel processing applications, the sub-streams drive independent jobs running on different compute cores.

Sampling Methods

The C++ standard library follows a typical design pattern for the facilities in its standard random header. It maintains a strict separation between the classes that produce random bits and others that use those bits.

Uniform random bit generators produce streams of uniformly distributed output words (usually 32-bit or 64-bit unsigned integers). Other classes and functions shape those core streams to simulate a desired distribution over some field of interest (for example, to generate variates from a uniform distribution of reals in the range 0 to 1).

The idea is reasonable enough. You can swap out the uniform random bit generator for a better one and continue to use the other functions without change.

However, this interface is quite complicated for the end user — particularly for a user with no idea, or interest in, how all the generator/distribution machinery works.

Perhaps she googles “c++ random number generator” and get advice to use the std::mersenne_twister_engine class. However, by itself, that class only produces unsigned output words that are useless by themselves. To simulate something as simple as a die roll, she must feed the thing into some other class and use that to get the required effect.

For this reason, we enrich our xoshiro/xoroshiro classes with some utility methods that interface directly with the various distribution classes in the standard random header. That dice roll becomes something as simple as:

xso::rng gen;
std::cout << "Six sided dice roll: " << gen.roll() << '\n';

Similarly, you can ask one of our generators to flip a coin or shuffle the elements in a container.

Our generators also directly support sampling. This includes pulling samples from a range, container, or an arbitrary distribution.

Here are some examples:

1std::cout << gen.sample(1, 10)   << '\n';
2std::cout << gen.sample(1., 10.) << '\n';

std::normal_distribution nd{70., 15.};
3std::cout << gen.sample(nd);

std::array<double, 10> v;
4gen.sample(nd, v.begin(), v.word_count());

std::array<double, 5> u;
5gen.sample(v, u.begin(), u.word_count());

6ge.shuffle(u);
1
Prints a random integer from \([1,10]\), where each integer is equally likely to occur.
2
Prints a random real from a uniform distribution over \([1,10)\)
3
Prints a random variate from a normal distribution with mean 70 and standard deviation 15.
4
Fills an array v with 10 random variates from that same distribution.
5
Fills an array u with 5 elements drawn from v without replacement.
6
Shuffles the elements of u.
Of course, the generators can also still be used as uniform random bit generators. The extra sampling methods etc. just make them much more useful out of the box.

Extra Analysis

Extra non-member functions for generator analysis are defined if the bit library is available.

bit is a C++ library for doing linear algebra over GF(2) the simplest Galois field of two elements \(\{0,1\}\), where the usual arithmetic operations are performed mod 2. The bit library is header-only, so it can be easily incorporated into any application.

If it is available, then xoshiro.h defines some extra functions that let you access the generator’s transition matrix and use/analyze that in various ways.

Even if you only use the single xoshiro.h header file without incorporating the bit library, you can still jump any of the predefined type-aliased xoshiro/xoroshiro variants by arbitrary numbers of steps.

Documentation

Here is a link to the project’s source code repository.
This documentation site was constructed using the static website generator Quarto.

Contact

You can contact me by email

Back to top