Reputation: 159
I was rewriting this function:
long rng(long low, long high)
{
return low + long((high - low + 1) * double(rand() / double(RAND_MAX + 1.0)));
}
Updating it to C++11 and Mersenne Twister.
long rng(long low, long high)
{
static std::mt19937 rnumber; // Mersenne Twister
std::uniform_int_distribution<unsigned> u (low, high);
return u(rnumber);
}
Now this seems to work, but my C++11 book states that in similar cases BOTH the engine and the distributor should be "static". The problem is that I want to call the function with different ranges, and if I make the distributor static then the range is always the same.
So how should this be handled in a proper way?
Two other questions:
Why Stroustrup book seems to suggest I should use {low, high} instead of (low, high)? What's the difference?
Is is plausible that with gcc 4.8.1 and no optimization rand is twice as fast as Mersenne, whereas with -O3 rand has the same performance, while Mersenne becomes even actually faster than rand?
Upvotes: 1
Views: 553
Reputation: 3351
You don't have to specify in advance the limits for the distribution.
#include <iostream>
#include <ostream>
#include <random>
unsigned long rng(unsigned long low, unsigned long high)
{
static std::mt19937 engine((std::random_device()()));
static std::uniform_int_distribution<unsigned long> dist;
std::uniform_int_distribution<unsigned long>::param_type p(low, high);
return dist(engine, p);
}
int main()
{
const unsigned int n = 10;
const int m = 24;
for (unsigned int k = 2; k != n + 1; ++k)
{
for (int j = 0; j != m; ++j)
{
// Print out 24 numbers from the range [0, k)
std::cout << rng(0, k - 1) << " ";
}
std::cout << std::endl;
}
return 0;
}
Upvotes: 0
Reputation: 105886
Distributions are allowed to calculate more than one random number when you use operator()
, as long as the number of calls of g.operator()
is amortized constant.
Therefore there is a significant difference between using a static distribution and a non-static one:
#include <iostream>
#include <random>
template <class Generator>
double use_rand(Generator & g){
std::normal_distribution<double> d;
return d(g);
}
template <class Generator>
double use_rand_static(Generator & g){
static std::normal_distribution<double> d;
return d(g);
}
int main(){
std::mt19937 rnumber{5};
std::cout << "non-static version:\n";
std::cout << use_rand(rnumber) << "\n";
std::cout << use_rand(rnumber) << "\n\n";
rnumber.seed(5);
std::cout << "static version:\n";
std::cout << use_rand_static(rnumber) << "\n";
std::cout << use_rand_static(rnumber) << std::endl;
return 0;
}
non-static version: 0.107794 -0.199707 static version: 0.107794 -0.0306363
As you can see the second values differ. The reason for this is hidden in the implementation of normal_distribution::operator()
, it calculates two values every other iteration.
This implementation defined behaviour is actually the reason why you should use the same distribution as long as you want to use values from the same probability space (aka static
). However, since you want to change low
and high
often you're changing the probability space, so it's fine to use a new distribution (creating most distributions is actually pretty cheap, but using a new one for every value can get expensive).
Upvotes: 2
Reputation: 153840
The distribution objects are objects which need some initialization. If you can reuse them, it will be more efficient to reuse them than creating them upon every use. Since the typical use of random numbers is to generate many values for the same ranges, the extra constructions may be removed. When replacing uses of rand()
with a different random number generator it is probably not viable to pass the objects through. For newly created code it may be reasonable to keep sufficient context around to have a distribution object, too.
With respect to your add on questions:
-O3
. There is, of course, also a chance that the compiler decides that you don't use the results from the random numbers and elides actually using it.Upvotes: 1