I have a set of functions in my program for conveniently generating random number in various distributions. I'm interested in the questions listed below, though other comments are also welcome.
- Am I using C++'s random tools correctly and clearly?
- From watching rand() Considered Harmful and reading this answer, I learned that RNG calls are not thread-safe. I changed my code just by adding the specifier
thread_local
to the generator and distribution variables. Is this all that's needed to make the functions thread-safe? These are called withinstd::async
procedures in my project. - Does it matter whether one
std::mt19937_64
generator is used for all the functions or if each function has its own copy?
In Random.h
#ifndef RANDOM_H
#define RANDOM_H
#include <random>
#include <algorithm>
namespace Random
{
// Random number with normal distribution and mean of zero
double random_normal(double standard_deviation);
// Random number with inclusive range
double random_real(double min, double max);
int random_integer(int min, int max);
// Return true with probability 50%
bool coin_flip();
// Return true with given probability
bool success_probability(double probability);
namespace
{
thread_local std::mt19937_64 generator(std::random_device{}());
}
// Shuffles the order of the list
template<class List>
void shuffle(List& list)
{
std::shuffle(list.begin(), list.end(), generator);
}
}
#endif // RANDOM_H
In Random.cpp
#include "Random.h"
#include <random>
int Random::random_integer(int min, int max)
{
using uid = std::uniform_int_distribution<int>;
thread_local static auto dist = uid{};
return dist(generator, uid::param_type{min, max});
}
double Random::random_normal(double standard_deviation)
{
using nd = std::normal_distribution<double>;
thread_local static auto dist = nd{};
return dist(generator, nd::param_type{0.0, standard_deviation});
}
double Random::random_real(double min, double max)
{
using urd = std::uniform_real_distribution<double>;
thread_local static auto dist = urd{};
return dist(generator, urd::param_type{min, max});
}
bool Random::coin_flip()
{
return success_probability(0.5);
}
bool Random::success_probability(double probability)
{
return random_real(0.0, 1.0) < probability;
}
An example usage:
#include <iostream>
#include <future>
#include "Random.h"
double random_walk(int number_of_steps, double step_size)
{
double position = 0;
for(int i = 0; i < number_of_steps; ++i)
{
position += Random::random_normal(step_size);
}
return position;
}
int main()
{
// Simulate 1,000,000-step continuous random walks
const int number_of_walks = 100; // adjust to suit your computer
const int number_of_steps = 1000000;
const double step_size = 1.0;
std::vector<std::future<double>> results;
for(int i = 0; i < number_of_walks; ++i)
{
results.emplace_back(std::async(random_walk, number_of_steps, step_size));
}
for(auto& result : results)
{
std::cout << result.get() << std::endl;;
}
return 0;
}