How to Generate Random ints and floats
algorithm
c++17
intermediate
C++ does not provide a simple “random” function, instead the C++ Standard Library provides three distinct components we must assemble to generate random numbers:
- A “random device”. These are intended to be implemented by the hardware your program is running on to provide non-deterministic random numbers.
- A “random number engine”. These are large state machines that generate pseudo-random numbers.
- A “random distribution”. These are lightweight functions that map an input number to a predetermined range of possible outputs.
Here’s a basic example putting the three pieces together:
#include <random> #include <iostream> int main() { // 1. The random device std::random_device rd; // 2. The "Mersenne Twister" random engine std::mt19937 gen(rd()); // 3. A random distribution std::uniform_int_distribution<int> dist(1, 20); for (int i = 0; i < 10; i ++) { std::cout << dist(gen) << " "; } std::cout << "\n"; }
16 16 13 3 20 16 11 14 20 9
Practical Random Numbers
The std::mt19937
Mersenne Twister is quite a large and non-trivial object while std::uniform_int_distribution
is a very small and lightweight object.
We can see this by using the sizeof
function to return the size in bytes of these objects.
#include <random> #include <iostream> int main() { std::random_device rd; std::mt19937 gen(rd()); std::uniform_int_distribution<int> dist(1, 100); std::cout << sizeof(rd) << " bytes\n"; std::cout << sizeof(gen) << " bytes\n"; std::cout << sizeof(dist) << " bytes\n"; }
4 bytes 2504 bytes 8 bytes
We also only used the std::random_device
once when we seeded our Mersenne Twister engine.
This means for repeated practical number generation it makes sense to create the random engine up front and re-use it many times.
There’s multiple ways you could do this, you could create a class that holds the engine and pass the class around to other objects that needed it, or you could have global state in your program.
Here’s an example using global state to re-use a Mersenne Twister for multiple different distributions:
#include <random> #include <iostream> // Construct a global Mersenne Twister to re-use // Use a temporary random_device to generate a single seed static std::mt19937 mersenneEngine(std::random_device{}()); template <typename IntType> IntType uniform_random_int(IntType a, IntType b) { std::uniform_int_distribution<IntType> dist(a, b); return dist(mersenneEngine); } template <typename RealType> RealType uniform_random_real(RealType a, RealType b) { std::uniform_real_distribution<RealType> dist(a, b); return dist(mersenneEngine); } int main() { for (int i = 0; i < 10; i++) { std::cout << uniform_random_int<int>(1, 6) << " "; } std::cout << "\n"; for (int i = 0; i < 5; i++) { std::cout << uniform_random_real<float>(0, 1) << " "; } std::cout << "\n"; }
2 3 1 4 1 3 3 4 2 4 0.780076 0.813484 0.524956 0.875319 0.333531
Of course, if you know you’re going to be re-using the same distribution over and over it makes sense to re-use that as well.
More Distributions
There’s a lot of distributions included with the C++ random library. The site cppreference.com has a great listing of distributions. As a last example a very common random distribution to use is a “normal” or “Gaussian” distribution. Here’s an example:
#include <algorithm> #include <random> #include <iostream> #include <iomanip> #include <math.h> int main() { std::random_device rd; std::mt19937 gen(rd()); std::normal_distribution<> dist; const int max = 3; // Generate 1000 random numbers from a normal distribution std::vector<int> hist(max * 2 + 1); for (int i = 0; i < 1000; i ++) { double val = std::clamp<double>(dist(gen), -max, max); hist[round(val) + max]++; } // Scale the histogram to the range [0-30] int max_sum = *std::max_element(hist.begin(), hist.end()); std::transform(hist.begin(), hist.end(), hist.begin(), [max_sum](auto& n) { return static_cast<float>(n) / max_sum * 30.0f; }); // Print out the histogram int i = -max; for (auto n : hist) { std::cout << std::setw(2) << i << "|" << std::string(n, '#') << "\n"; i++; } }
-3| -2|#### -1|###################### 0|############################## 1|#################### 2|##### 3|