The first one works, but the second one always returns the same value. Why would this happen and how am I supposed to fix this?
int main() {
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_real_distribution<> dis(0, 1);
for(int i = 0; i < 10; i++) {
std::cout << dis(gen) << std::endl;
}return 0;
}
The one dosen't work:
double generateRandomNumber() {
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_real_distribution<> dis(0, 1);
return dis(gen);
}
int main() {
for(int i = 0; i < 10; i++) {
std::cout << generateRandomNumber() << std::endl;
}return 0;
}
What platform are you working on?
std::random_device
is allowed to be a pseudo-RNG if hardware or OS functionality to generate random numbers doesn't exist. It might initialize using the current time, in which case the intervals at which you're calling it might be too close apart for the 'current time' to take on another value.Nevertheless, as mentioned in the comments, it is not meant to be used this way. A simple fix will be to declare
rd
andgen
asstatic
. A proper fix would be to move the initialization of the RNG out of the function that requires the random numbers, so it can also be used by other functions that require random numbers.Note that
std::mt19937 gen(rd())
is very problematic. See this question, which says:Furthermore,
random_device
's approach to generating "nondeterministic" random numbers is "implementation-defined", andrandom_device
allows the implementation to "employ a random number engine" if it can't generate "nondeterministic" random numbers due to "implementation limitations" ([rand.device]). (For example, under the C++ standard, an implementation might implementrandom_device
using timestamps from the system clock, or using fast-moving cycle counters, since both are nondeterministic.)An application should not blindly call
random_device
's generator (rd()
) without also, at a minimum, calling theentropy()
method, which gives an estimate of the implementation's entropy in bits.The first one uses the same generator for all the numbers, the second creates a new generator for each number.
Let's compare the differences between your two cases and see why this happening.
Case 1:
In your first case the program executes the main function and the first thing that happens here is that you are creating an instance of a
std::random_device
,std::mt19337
and astd::uniform_real_distribution<>
on the stack that belong tomain()
's scope. Your mersenne twistergen
is initialized once with the result from your random devicerd
. You have also initialized your distributiondis
to have the range of values from0
to1
. These only exist once per each run of your application.Now you create a for loop that starts at index
0
and increments to9
and on each iteration you are displaying the resulting value tocout
by using the distributiondis
'soperator()()
passing to it your already seeded generationgen
. Each time on this loopdis(gen)
is going to produce a different value becausegen
was already seeded only once.Case 2:
In this version of the code let's see what's similar and what's different. Here the program executes and enters the
main()
function. This time the first thing it encounters is a for loop from0
to9
similar as in the main above however this loop is the first thing on main's stack. Then there is a call tocout
to display results from auser defined function
namedgenerateRandomNumber()
. This function is called a total of10
times and each time you iterate through the for loop this function has its own stack memory that will be wound and unwound or created and destroyed.Now let's jump execution into this
user defined function
namedgenerateRandomNumber()
.The code looks almost exactly the same as it did before when it was in
main()
directly but these variables live ingenerateRandomNumber()
's stack and have the life time of its scope instead. These variables will be created and destroyed each time this function goes in and out of scope. The other difference here is that this function also returnsdis(gen)
.Finally when then function
generateRandomNumber()
returns and just before it goes completely out of scope wherestd::uniform_real_distribrution<>
'soperator()()
is being called and it goes into it's own stack and scope before returning back to maingenerateRandomNumber()
ever so briefly and then back to main.-Visualizing The Differences-
As you can see these two programs are quite different, very different to be exact. If you want more visual proof of them being different you can use any available online compiler to enter each program to where it shows you that program in
assembly
and compare the two assembly versions to see their ultimate differences.Another way to visualize the difference between these two programs is not only to see their
assembly
equivalents but to step through each program line by line with adebugger
and keep an eye on thestack calls
and the winding and unwinding of them and keep an eye of all values as they become initialized, returned and destroyed.-Assessment and Reasoning-
The reason the first one works as expected is because your
random device
, yourgenerator
and yourdistribution
all have the life time ofmain
and yourgenerator
is seeded only once with your random device and you only have one distribution that you are using each time in the for loop.In your second version main doesn't know anything about any of that and all it knows is that it is going through a for loop and sending returned data from a user function to cout. Now each time it goes through the for loop this function is being called and it's stack as I said is being created and destroyed each time so all if its variables are being created and destroyed. So in this instance you are creating and destroying
10:
rd
,gen(rd())
, anddis(0,1)
s instances.-Conclusion-
There is more to this than what I have described above and the other part that pertains to the behavior of your random number generators is what was mentioned by user
Kane
in his statement to you from his comment to your question:Each time you create and destroy you are seeding the
generator
over and over again with a newrandom_device
however if your particular machine or OS doesn't have support for usingrandom_device
it can either end up using some arbitrary value as its seed value or it could end up using the system clock to generate a seed value.So let's say it does end up using the system clock, the execution of
main()
's for loop happens so fast that all of the work that is being done by the10
calls togenerateRandomNumber()
is already executed before a few milliseconds have passed. So here the delta time is minimally small and negligible that it is generating the same seed value on each pass as well as it is generating the same values from the distributions.