Using the Minimal C Implementation

The C minimal implementation is the simplest of all the PCG implementations. It has the fewest features, but for many users, it provides everything you need. The interface is similar to the Unix rand/rand_r and random/random_r interfaces.

Playing with the Code

If you've not done so already download the code and build it, then run one of the sample programs, such as pcg32-demo. If you run it, it should produce the following output (always the same, because it uses a fixed seed):

unix% ./pcg32-demo
pcg32_random_r:
      -  result:      32-bit unsigned int (uint32_t)
      -  period:      2^64   (* 2^63 streams)
      -  state type:  pcg32_random_t (16 bytes)
      -  output func: XSH-RR

Round 1:
  32bit: 0xa15c02b7 0x7b47f409 0xba1d3330 0x83d2f293 0xbfa4784b 0xcbed606e
  Coins: HHTTTHTHHHTHTTTHHHHHTTTHHHTHTHTHTTHTTTHHHHHHTTTTHHTTTTTHTTTTTTTHT
  Rolls: 3 4 1 1 2 2 3 2 4 3 2 4 3 3 5 2 3 1 3 1 5 1 4 1 5 6 4 6 6 2 6 3 3
  Cards: Qd Ks 6d 3s 3d 4c 3h Td Kc 5c Jh Kd Jd As 4s 4h Ad Th Ac Jc 7s Qs
         2s 7h Kh 2d 6c Ah 4d Qh 9h 6s 5s 2c 9c Ts 8d 9s 3c 8c Js 5d 2h 6h
         7d 8s 9d 5h 8h Qc 7c Tc
⋮
⋮
Round 5:
  32bit: 0xfcef7cd6 0x1b488b5a 0xd0daf7ea 0x1d9a70f7 0x241a37cf 0x9a3857b7
  Coins: HHHHTHHTTHTTHHHTTTHHTHTHTTTTHTTHTHTTTHHHTHTHTTHTTHTHHTHTHHHTHTHTT
  Rolls: 5 4 1 2 6 1 3 1 5 6 3 6 2 1 4 4 5 2 1 5 6 5 6 4 4 4 5 2 6 4 3 5 6
  Cards: 4d 9s Qc 9h As Qs 7s 4c Kd 6h 6s 2c 8c 5d 7h 5h Jc 3s 7c Jh Js Ks
         Tc Jd Kc Th 3h Ts Qh Ad Td 3c Ah 2d 3d 5c Ac 8s 5s 9c 2h 6c 6d Kh
         Qd 8d 7d 2s 8h 4h 9d 4s

This example code shows some common RNG use cases; it

  • Prints out some 32-bit random numbers
  • Tosses some coins (i.e., performs a binary choice)
  • Rolls some dice (i.e., produces numbers in the range 1-6, without introducing bias or destroying uniformity)
  • Shuffles and deals some playing cards

Looking at the code ought to tell you everything you need to know about using the C library.

API

The minimal C library provides just one member of the PCG family, a 32-bit generator that uses two 64-bit integers for its internal state, one provides the internal state of the RNG, and is user-selectable constant that controls the generation algorithm—different values for this constant cause the generator to produce a different (and unique) sequence of random numbers (sometimes called the stream).

You can create as many separate RNGs as you like. So long as you give them different sequence selection constants, they will be independent and uncorrelated with each other. The code in pcg32x2-demo.c shows an eample of ganging together two independent 32-bit RNGs to make a 64-bit RNG. (The full library provides 64-bit RNGs directly.)

As a convenience, the library also provides a global RNG. (No attempt is made to arbitrate access to this global; state—multithreaded programs should either perform their own locking, or, far more sensibly, provided each thread with its own, independent, RNG.)

The API centers around these functions:

void pcg32_srandom_r(pcg32_random_t* rngptr, uint64_t initstate, uint64_t initseq)
uint32_t pcg32_random_r(pcg32_random_t* rngptr)
uint32_t pcg32_boundedrand_r(pcg32_random_t* rngptr, uint32_t bound)

and these variants for the global RNG:

void pcg32_srandom(uint64_t initstate, uint64_t initseq)
uint32_t pcg32_random()
uint32_t pcg32_boundedrand(uint32_t bound)

These functions, and the pcg32_random_t type, are declared by

#include <pcg-basic.h>

pcg32_srandom_r(rngptr, initstate, initseq)

Every RNG you use should be initialized (seeded) somewhere in your code and this is the function that does it. Typically, you should only seed a RNG once (and the number of seedings should be much much less than the number of random numbers generated).

  • rngptr should point to the address of a pcg32_random_t value that you've previously declared
  • initstate is the starting state for the RNG, you can pass any 64-bit value.
  • initseq selects the output sequence for the RNG, you can pass any 64-bit value, although only the low 63 bits are significant.

Calling pcg32_srandom_r with the same arguments produces the same output.

If you want truly nondeterministic output for each run of your program, you should pass values that will be different from run to run. On a Unix system, /dev/random provides random bytes that can be used for initialization (the full C library provides a handy wrapper function entropy_getbytes to help), but if you want a quick and dirty way to do the initialization, one option is to pass the current time and the address of the RNG itself.

For example, this code initializes three RNGs, and they really do produce different values even though the time probably doesn't advance between calls.

pcg32_random_t rng1, rng2, rng3;
pcg32_srandom_r(&rng1, time(NULL), (intptr_t)&rng1);
pcg32_srandom_r(&rng2, time(NULL), (intptr_t)&rng2);
pcg32_srandom_r(&rng3, time(NULL), (intptr_t)&rng3);

(The full C library provides a type pcg32u_random_t and an associated set of functions that always use the address of the RNG for the current stream, reducing the space usage for the RNG state by half.)

The function pcg32_srandom(initstate, initseq) behaves identically, but uses the global RNG.

If for some reason you cannot call pcg32_srandom_r, you can initialize a variable using the special constant PCG32_INITIALIZER which expands a standard C brace-initialization constant. Failing to correctly initialize the state can result in a generator with improper behavior (for example, all-zeros initialization will produce a generator that always returns zero).

pcg32_random_r(rngptr)

Generates a uniformly distributed 32-bit unsigned integer (i.e., x where, 0 <= x < 2^32).

The function pcg32_random() behaves identically, but uses the global RNG.

pcg32_boundedrand_r(rngptr, bound)

Generates a uniformly distributed 32-bit unsigned integer less than bound (i.e., x where 0 <= x < bound).

Some programmers may think that they can just run pcg32_random_r(rng) % bound, but doing so introduces nonuniformity when bound is not a power of two. The code for pcg32_boundedrand_r avoids the nonuniformity by dropping a portion of the RNG's output.

The function pcg32_boundedrand(bound) behaves identically, but uses the global RNG.

Share