Using the PCG C++ Implementation

The C++ implementation provides the canonical reference implementation of the PCG family. It supports all family members, including extended generators, and offers the most flexibility. Despite its extreme flexibility, it's also easy to use. (If you'd like to look at code, however, you'd be better off starting with the minimal C library because that there is considerably less code.)

Basics

You can use the PCG generation scheme exactly like other C++11-style generators (see, for example, Walter Brown's Random Number Generation in C++11 [PDF, WG21 N3551]).

Let's begin by taking some C++11 example code from cppreference.com, and then switching it over to the PCG scheme.

#include <iostream>
#include <iomanip>
#include <string>
#include <map>
#include <random>
#include <cmath>

int main()
{
// Seed with a real random value, if available
std::random_device rd;

// Choose a random mean between 1 and 6
std::default_random_engine e1(rd());
std::uniform_int_distribution<int> uniform_dist(1, 6);
int mean = uniform_dist(e1);
std::cout << "Randomly-chosen mean: " << mean << '\n';

// Generate a normal distribution around that mean
std::mt19937 e2(rd());
std::normal_distribution<> normal_dist(mean, 2);

// Produce histogram
std::map<int, int> hist;
for (int n = 0; n < 10000; ++n) {
++hist[std::round(normal_dist(e2))];
}
std::cout << "Normal distribution around " << mean << ":\n";
for (auto p : hist) {
std::cout << std::fixed << std::setprecision(1) << std::setw(2)
<< p.first << ' ' << std::string(p.second/30, '*') << '\n';
}
}

which produces output like this:

Randomly-chosen mean: 3
Normal distribution around 3:
-4
-3
-2 ***
-1 *********
0 *********************
1 ****************************************
2 *********************************************************
3 *******************************************************************
4 ********************************************************
5 *****************************************
6 *********************
7 **********
8 ***
9
10
11

We can switch this code over to PCG generation by adding #include "pcg_random.hpp" and using a PCG generator, such as pcg32 rather than std::default_random_engine and std::mt19937.

There is something worth noting about the above example code. Although it uses std::random_device for seeding, it initializes the generators with just one 32-bit random int from system's random device. In the case of std::mt19937, that's somewhat problematic because this generator uses 624 32-bit integers to provide 19937 bits of internal state, yet the code initializes it with a mere 32 bits of entropy. Why? Because C++11 (and C++14) makes proper initialization needlessly difficult. The PCG library makes it easier, providing seed_seq_from. The code below uses this feature, and some more features of the PCG library.

#include <iostream>
#include <iomanip>
#include <string>
#include <map>
#include <random>
#include <cmath>
#include "pcg_random.hpp"

int main()
{
// Seed with a real random value, if available
pcg_extras::seed_seq_from<std::random_device> seed_source;

// Make a random number engine
pcg32 rng(seed_source);

// Choose a random mean between 1 and 6
std::uniform_int_distribution<int> uniform_dist(1, 6);
int mean = uniform_dist(rng);
std::cout << "Randomly-chosen mean: " << mean << '\n';

// Generate a normal distribution around that mean
std::normal_distribution<> normal_dist(mean, 2);

// Make a copy of the RNG state to use later
pcg32 rng_checkpoint = rng;

// Produce histogram
std::map<int, int> hist;
for (int n = 0; n < 10000; ++n) {
++hist[std::round(normal_dist(rng))];
}
std::cout << "Normal distribution around " << mean << ":\n";
for (auto p : hist) {
std::cout << std::fixed << std::setprecision(1) << std::setw(2)
<< p.first << ' ' << std::string(p.second/30, '*') << '\n';
}

// Produce information about RNG usage
std::cout << "Required " << (rng - rng_checkpoint) << " random numbers.\n";
}

Which produces output like this:

Randomly-chosen mean: 4
Normal distribution around 4:
-3
-2
-1 **
0 *********
1 **********************
2 **************************************
3 ************************************************************
4 *****************************************************************
5 **********************************************************
6 *****************************************
7 *********************
8 *********
9 ***
10
11
12
Required 25560 random numbers.

Notice that we've added a feature to the code. We can now determine how many random numbers std::normal_distribution needed to use from the base generator to produce normally distributed output. What's neat about this feature is that it didn't explicitly count how many times the generator was called, the difference is algorithmically determined by comparing the internal states of the two generators.

The above program runs quickly, but if we increase the constants, from 10,000 iterations and 30 numbers per star to 100,000,000 iterations and 300,000 per star, we can finally get a program that takes a noticeable time to run. It takes about 4.5 seconds on my laptop. In contrast, std::mt19937 requires 5.75 seconds (that's about 28% more time), and if we use std::minstd_rand instead, it takes 5.35 seconds (about 19% more time, despite being a statistically terrible generator).

And that shows you the essence of the C++ PCG generation library. It provides all the C++ generation facilities, plus some very handy extras. Plus, it's very fast, very space efficient and statistically excellent.

C++11 Standard Features

All the PCG generators provide the usual interface we'd expect for a random number generator. We'll use pcg32 as a running example, but these operations are supported by all PCG generators.

result_type

The integer type used for random values. pcg32::result_type is uint32_t.

state_type

The integer type used for the internal state of the generator. pcg32::state_type is uint64_t.

pcg32::pcg32(...) (constructor)

We support the following constructors:

pcg32()
default construct using a fixed seed (and a fixed stream)
explicit pcg32(state_type seed)
construct with the given seed (and a fixed stream)
pcg32(state_type seed, state type stream)
construct with the given seed and stream (only available on generators, such as pcg32, that support multiple streams); this is not a C++11 required feature
template <typename SeedSeq> pcg32(SeedSeq&& seq)
initialize using the provided seed-sequence object
pcg32(const pcg32& orig)
make a copy of orig

void pcg32::seed(...)

Reseed the generator. The arguments to the seed operation are identical to those passed to the constructor.

result_type pcg32::operator()

Advances the engine's state and a new pseudorandom value.

Advances the engine's state by n. For all generators except non-equidistributed extended generators, it is a call to advance and takes O(log n) time.

result_type pcg32::min()

The least possible random value. Always returns zero.

result_type pcg32::max()

The greatest possible random value. Always returns the largest possible integer of result_type.

bool operator==(const pcg32& lhs, const pcg32& rhs)

Compare two RNG states for equality. Two generators are equal if they have the same state and represent the same random stream.

bool operator!=(const pcg32& lhs, const pcg32& rhs)

Opposite of operator==.

std::ostream& operator<<(std::ostream& out, const pcg32& rng)

Outputs the internal state to an ostream.

std::istream& operator>>(std::istream& out, const pcg32& rng)

Gets the internal state from an ostream.

Extra Features

size_t period_pow2

Returns the period of the generator as a power of two. For example, for pcg32, it returns 64, indicating a period of 264.

result_type pcg32::operator()(result_type upper_bound)

This function provides exactly the interface imagined by the designers of C++03's random_shuffle algorithm, it generates a random number n such that 0 <= n < upper_bound. In other words, it lets us write:

auto chosen_card = cards[rng(52)];

rather than

typedef std::uniform_int_distribution<rng::result_type> distribution_t;
distribution_t dist;
typedef typename distribution_t::param_type param_t;

auto chosen_card = cards[dist(g, param_t(0, 51))]

C++'s distribution classes are very-full featured, but for quick-and-simple programs, they often feel like overkill.

It also tends to be about twice as fast as GCC or Clang's implementation of std::uniform_int_distribution (which has to handle a wider variety of RNGs).

void pcg32::set_stream(state_type stream)

Allows you to choose the stream. It provides an alternative to passing the stream to the constructor or seed function (but the single-argument call to seed resets the stream to the default, so you would need to call set_stream after the call to seed).

size_t streams_pow2

Returns the number of streams as a power of two. For pcg32, it returns 63, indicating 263 streams.

bool wrapped()

Tells you when the RNG wraps around to the beginning (which we define as the zero state).

Advances the generator forward delta steps, but does so in logarithmic time. (Not available for non-equidistributed extended generators.)

void backstep(state_type delta)

Move the generator backwards delta steps, but does so in logarithmic time. (Not available for non-equidistributed extended generators.)

state_type operator-(const pcg32& lhs, const pcg32& rhs)

Returns how much we would need to advance rhs for it to be equal to lhs. The generators must belong to the same stream.

This facility is not provided for the extended generators; a typical extended generator will have such a huge period that the delta would not fit in a simple variable.

Features for Extended Generators

We'll use pcg32_k64, a 64-dimensionally equidistributed generator, as our running example.

state_type pcg32_k64::pcg32_k64(...)

In addition to the usual constructors, extended generators also provide

explicit pcg32_k64(const result_type* data, state_type seed)
construct with the given seed (and a fixed stream), initialize the extended data using the array data. For pcg32_k64, the array must hold 64 entries.
pcg32(const result_type* data, state_type seed, state type stream)
as above, but construct with the given seed and stream (only available on generators, such as pcg32, that support multiple streams)

void pcg32_k64::set(result_type desired)

Adjust the generator state to create a new state that has just produced desired (yet leaving values within 63 either side of our position unaffected). This function allows you to create “party-trick” generators such as the one used in the code below.

#include <cstdio>
#include <cstddef>
#include <cstdint>
#include <iostream>
#include <sstream>
#include <string>
#include <random>

#include <unistd.h>

#include "pcg_random.hpp"

static const char* saved_state =
"6364136223846793005 3503324247726078831 6557656048857751321 103238831 "
"665891259 1902651333 4073047566 368781010 3371458373 3520911659 1176018374 "
"1290944887 2479283234 2214499777 3287447736 4241043352 2808175048 83300271 "
"162496091 3372211384 3773661488 3842517107 154403914 1983905875 185363760 "
"3574548828 4259275054 2055322655 3183516320 3827707798 2358810643 3947601356 "
"1518701804 2987610801 4256672123 243420444 2418646926 1593945712 3293969771 "
"1047458160 4148325853 4134598831 813996594 2374617805 712898811 2110551176 "
"233031372 1753202862 281911517 1950853967 3790278509 4176603202 4256155456 "
"1413186342 1718872307 2898301505 1732438719 622306094 366401535 2963949396 "
"2676833081 98878999 999895120 425860638 4096143638 4063627507 2566817785";

int main()
{
pcg32_k64 rng;

std::istringstream inbuf(saved_state);
inbuf >> rng;
if (inbuf.fail())
abort();

constexpr size_t BUFFER_SIZE = 1024ull * 128ull;
uint32_t buffer[BUFFER_SIZE];
constexpr size_t ROUNDS = 1024ull * 1024ull * 1024ull / sizeof(buffer);

for (size_t i = 0; i < ROUNDS; ++i) {
for (auto& v : buffer)
v = rng();
write(1, (void*) buffer, sizeof(buffer));
}

return 0;
}

It produces 1MB of random looking data, and then for just a short while, things get interesting. For example, here's part of the output:

001012f0  e6 55 40 70 ef 21 7b 9a  e7 1d 0e 3c 5b b7 3d b4  |.U@p.!{....<[.=.|
00101300  66 8c 36 b1 65 b7 e7 a7  3f 12 8c e2 1c 14 a8 52  |f.6.e...?......R|
00101310  b8 c8 84 15 43 3a bb 72  72 22 09 64 76 67 03 33  |....C:.rr".dvg.3|
00101320  c6 ff 4a 3d 2b 7e d4 17  11 d6 81 37 08 b7 1f 4a  |..J=+~.....7...J|
00101330  55 6e 6c 69 6b 65 6c 79  20 74 68 69 6e 67 73 20  |Unlikely things |
00101340  68 61 70 70 65 6e 2c 20  72 69 67 68 74 3f fe 83  |happen, right?..|
00101350  ed b3 4a 0d e2 ea f3 c1  70 a7 b6 8b db 5a d8 92  |..J.....p....Z..|
00101360  9b 1f 09 0f bf 12 6f 7a  46 8b 12 ba e1 1e a6 9e  |......ozF.......|
00101370  ef a9 0c ec dd 52 47 c5  82 26 a8 e8 d7 50 9a b9  |.....RG..&...P..|

Feel free to run the program, skip 1MB of output, and see what else you find.

It may seem strange to find some English text in the output of a random number generator, but that's what pcg32_k64's 64-dimensional equidistribution guarantees you—that every possible sequence of 64 32-bit values (i.e., 256 bytes) will occur in its staggering 22112 period, in fact they each occur 264 times. The only difficulty is finding such sequences in all the noise. The set operation means that you don't have to find such a state, you can create it.

Available Generators

The library provides convenience typedefs to provide the following generators

32-Bit Generators with 64-Bit State

pcg32
32-bit generator, 264 period, 263 streams
pcg32_oneseq
32-bit generator, 264 period, one stream
pcg32_unique
32-bit generator, 264 period, every instance has its own unique stream (for I/O, you can output this generator and then read it back into a pcg32 generator)
pcg32_fast
32-bit generator, 262 period, one stream; faster but slightly less statistically good output function (nevertheless still very good)

64-Bit Generators with 128-Bit State

pcg64
64-bit generator, 2128 period, 2127 streams
pcg64_oneseq
64-bit generator, 2128 period, one stream
pcg64_unique
64-bit generator, 2128 period, every instance has its own unique stream (for I/O, you can output this generator and then read it back into a pcg64 generator)
pcg64_fast`
64-bit generator, 2126 period, one stream

Insecure Generators

These generators only output each number exactly once during their period. They are compact, and are statistically excellent, but should not be used except in specialized scenarios.

pcg8_once_insecure
8-bit generator, 28 period, 27 streams
pcg16_once_insecure
16-bit generator, 216 period, 215 streams
pcg32_once_insecure
32-bit generator, 232 period, 231 streams
pcg64_once_insecure
64-bit generator, 264 period, 263 streams
pcg128_once_insecure
128-bit generator, 2128 period, 2127 streams
pcg8_oneseq_once_insecure
8-bit generator, 28 period, one stream
pcg16_oneseq_once_insecure
16-bit generator, 216 period, one stream
pcg32_oneseq_once_insecure
32-bit generator, 232 period, one stream
pcg64_oneseq_once_insecure
64-bit generator, 264 period, one stream
pcg128_oneseq_once_insecure
128-bit generator, 2128 period, one stream

Extended 32-Bit Generators

pcg32_k2
32-bit 2-dimensionally equidistributed generator, 2128 period, 263 streams
pcg32_k2_fast
32-bit 2-dimensionally equidistributed generator, 2128 period, one stream
pcg32_k64
32-bit 64-dimensionally equidistributed generator, 22112 period, 263 streams (about the same state size and period as arc4random)
pcg32_c64
32-bit generator, 22112 period, 263 streams; uniform but not equidistributed; harder to predict than the above generator
pcg32_k1024
32-bit 64-dimensionally equidistributed generator, 232832 period, 263 streams (larger period than the mersenne twister)
pcg32_c1024
32-bit generator, 232832 period, 263 streams; uniform but not equidistributed; harder to predict than the above generator
pcg32_k16384
32-bit generator, 2524352 period, 263 streams; useful for extreme party tricks

Extended 64-Bit Generators

These generators are not currently supported on machines without native 128-bit math. (Adding support requires C++14's enhanced constexpr features.).

pcg64_k32
64-bit 32-dimensionally equidistributed generator, 22176 period, 2127 streams (about the same state size and period as arc4random)
pcg64_k64
64-bit generator, 22176 period, 2127 streams; uniform but not equidistributed; harder to predict than the above generator
pcg64_k1024
64-bit 64-dimensionally equidistributed generator, 265664 period, 263 streams (larger period than the mersenne twister)
pcg32_c1024
64-bit generator, 265664 period, 263 streams; uniform but not equidistributed; harder to predict than the above generator