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:

  1. A “random device”. These are intended to be implemented by the hardware your program is running on to provide non-deterministic random numbers.
  2. A “random number engine”. These are large state machines that generate pseudo-random numbers.
  3. 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 8 11 9 9 13 9 4 13 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";
}
3 4 1 1 5 2 2 5 5 6 
0.722497 0.290688 0.0378146 0.49841 0.765133 

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|

For more C++ By Example, click here.