The Source for Our randuint32 Library
This is bonus material. You don't need to look at this code.
Header File: randuint32.hpp
/* randuint32.hpp
==============
Interface definition for the RandUInt32 class, which provides a simple
way to get random numbers.
This file is provided as part of CS 70, but may be used in other
code.
*/
#ifndef RANDUINT32_HPP_INCLUDED
#define RANDUINT32_HPP_INCLUDED
#include <iostream>
#include <cstdint>
#include "jsf.hpp"
/**
* \class RandUInt32
* \brief Provides a simplified way to get random numbers.
*
* \details
* Contains a C++-style a random number engine as a data member that it
* uses to produce the numbers.
*
*/
class RandUInt32 {
public:
/**
* \brief Initializes a RandUInt32 object with a random seed from
* system entropy.
*/
RandUInt32();
/**
* \brief Initializes a RandUInt32 object with user-provided seed.
* \param seedval the seed
*/
RandUInt32(uint64_t seedval);
/**
* \brief Get a random value in the range [0,max) (i.e., not including
* max).
* \param max the past-the-end value
*/
uint32_t get(size_t max);
/**
* \brief Get a 32-bit integer.
* \warning Do not run `mygen.get() % range`, use `mygen.get(range)`
* instead. The former is both slower and also biased.
*/
uint32_t get();
private:
typedef jsf32 rng_type; // Can be swapped out for another C++-style PRNG
rng_type rng_; // The size of this type depends on this data
// member.
};
#endif
The class is designed so that we could change rng_type
to be a different PRNG. Currently it uses jsf32
, but it could for example use std::minstd_rand
or std::mt19937
instead.
Source File: randuint32.cpp
The following would be entirely sufficient:
/* randuint32.cpp
==============
Implementation of the RandUInt32 class, which provides a simple way
to get random numbers.
This file is provided as part of CS 70, but may be used in other
code.
*/
#include "randuint32.hpp"
#include <chrono>
#include <random>
/*
* \brief Generates 64 bits of system entropy, based on the two calls to
* the random device and one to the high-resolution clock.
* 64 bits is about the bare minimum for a generic random seed.
*
* \note More about seeding can be found at
* https://www.pcg-random.org/categories/seeding.html
*/
static uint64_t systemEntropy() {
// Set up 64-bits of randomness from the random device.
std::random_device rdev;
uint64_t pool = rdev();
pool <<= 32;
pool |= rdev();
// Xor in more entropy from the system clock, in case std::random_device
// doesn't actually produce system entropy (it's not required to!!).
// See https://www.pcg-random.org/posts/cpps-random_device.html
pool ^=
std::chrono::high_resolution_clock::now().time_since_epoch().count();
return pool;
}
RandUInt32::RandUInt32() : rng_(systemEntropy()) {
// Nothing (else) to do.
}
RandUInt32::RandUInt32(uint64_t seedval) : rng_(seedval) {
// Nothing (else) to do.
}
uint32_t RandUInt32::get(size_t max) {
std::uniform_int_distribution<uint32_t> udist(0, max - 1);
return udist(rng_);
}
uint32_t RandUInt32::get() {
std::uniform_int_distribution<uint32_t> udist(0, UINT32_MAX);
return udist(rng_);
}
If you want to have you're own version of RandUInt32
to use, the one above
might be the best, because the code is the simplest.
For this class though, we like to give examples where we can know for sure
what the output would be for a given seed. The std::uniform_int_distribution
class template is not guaranteed to produce the same results on different standard library implementations (or even different versions of the same library). To have identical results across systems, we avoid using std::uniform_int_distribution
and instead implement its functionality ourselves (unless you switch the underlying generator to something that makes doing that super-awkward).
Generally though, reimplementing code that is already in the standard library is a bad idea. Often considerable engineering effort has gone into making the standard library code perform very well (although obviously that cany vary from system to system).
The code below uses various recent C++ features we've not covered in this class, including templated (compile-time) constants, and if constexpr
which is an if
whose outcome must be determined at compile-time.
/* randuint32.cpp
==============
Implementation of the RandUInt32 class, which provides a simple way
to get random numbers.
This file is provided as part of CS 70, but may be used in other
code.
*/
#include "randuint32.hpp"
#include <chrono>
#include <random>
#include <limits>
/*
* \brief Generates 64 bits of system entropy, based on the two calls to
* the random device and one to the high-resolution clock.
* 64 bits is about the bare minimum for a generic random seed.
*
* \note More about seeding can be found at
* https://www.pcg-random.org/categories/seeding.html
*/
static uint64_t systemEntropy() {
// Set up 64-bits of randomness from the random device.
std::random_device rdev;
uint64_t pool = rdev();
pool <<= 32;
pool |= rdev();
// Xor in more entropy from the system clock, in case std::random_device
// doesn't actually produce system entropy (it's not required to!!).
// See https://www.pcg-random.org/posts/cpps-random_device.html
pool ^=
std::chrono::high_resolution_clock::now().time_since_epoch().count();
return pool;
}
/*
* \brief C++ allows PRNGs to have completely arbitrary output ranges, for
* example, you could have one that outputs only 7 distinct values.
* This code provides a compile-time test to make sure the PRNG output
* uses the full range of the type and at least 32-bits.
* Modern PRNGs are all full-range.
*/
template <typename PRNG>
static constexpr bool is_fullwidth_prng =
PRNG::min() == 0 && PRNG::max() >= std::numeric_limits<uint32_t>::max() &&
PRNG::max() == ~(typename PRNG::result_type(0));
RandUInt32::RandUInt32() : rng_(systemEntropy()) {
// Nothing (else) to do.
}
RandUInt32::RandUInt32(uint64_t seedval) : rng_(seedval) {
// Nothing (else) to do.
}
uint32_t RandUInt32::get(size_t max) {
// The downside of using std::uniform_int_distribution is that
// a) It's super generic, it can even work with a PRNG that only outputs
// random numbers in the range 42 to 56. That makes it a bit slow.
// b) The implementation is up to the standard library, so different
// standard libraries produce different outputs. For portable
// reproducibility, we need to write our own.
// So we avoid it if we can.
// static_assert(is_fullwidth_prng<rng_type>, "PRNG is not full-width");
// ^-- uncomment this line to ensure only full-width PRNGs may be used
if constexpr (is_fullwidth_prng<rng_type>) {
// This code is rom https://www.pcg-random.org/posts/bounded-rands.html
// It's the fastest known algorithm for unbiased random numbers in a
// range. It's a little more complex than other techniques.
uint32_t x = rng_();
uint64_t m = uint64_t(x) * uint64_t(max);
uint32_t l = uint32_t(m);
if (l < max) {
uint32_t t = -max;
if (t >= max) {
t -= max;
if (t >= max) {
t %= max;
}
}
while (l < t) {
x = rng_();
m = uint64_t(x) * uint64_t(max);
l = uint32_t(m);
}
}
return m >> 32;
} else {
std::uniform_int_distribution<uint32_t> udist(0, max - 1);
return udist(rng_);
}
}
uint32_t RandUInt32::get() {
if constexpr (is_fullwidth_prng<rng_type>) {
// If so, we don't need to do anything to the result.
return rng_();
} else {
std::uniform_int_distribution<uint32_t> udist(
0, std::numeric_limits<uint32_t>::max());
return udist(rng_);
}
}
Queries
What's
jsf32
?It's a C++-style PRNG that is fast and compact. The original algorithm was invented by Bob Jenkins and implemented in C, but we use this C++ implementation.
Are there other generators I can use?
Yes, a lot. There are a number built into the C++ standard library, but they tend to be older generators that have various flaws.
The
std::mt19937
generator (a.k.a. The Mersenne Twister) is pretty good, but uses up a lot of space -- at least 19937 bits or about 2.5 kiB, and some implementations use twice that to gain added speed.Technically, the Mersenne Twister fails some PRNG test suites (failing the Matrix Rank and Linear Complexity tests).
What about Prof. Melissa's PRNG?
You can download a C++ implementation of PCG from GitHub.
Why don't you use it here?
Prof. Melissa, as a matter of principle, prefers not to require anyone to use her PRNG.
(When logged in, completion status appears here.)