Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
330 views
in Technique[技术] by (71.8m points)

c++ - C++11 std::generate and std::uniform_real_distribution called two times gives strange results

calling std::generate algorithm from the STL two times on different containers produces equivalent results.

Consider I want to fill up two float arrays with random numbers between -1. and 1. :

std::array<float, 1000> x;
std::array<float, 1000> y;

std::random_device rd;
std::mt19937_64 gen(rd());
std::uniform_real_distribution<float> dis(-1.f, 1.f);
auto rand = std::bind(dis, gen);
std::generate(x.begin(), x.end(), rand);
std::generate(y.begin(), y.end(), rand);

You can test it here : http://ideone.com/X712IU. Both arrays are filled up with the exact same values :

0:  -0.411968,  -0.411968
1:    0.55158,    0.55158
2:    0.69889,    0.69889
3:  -0.901328,  -0.901328
4:  -0.556142,  -0.556142
5:  -0.798431,  -0.798431
6:  -0.570874,  -0.570874
7:   0.928999,   0.928999
8:   0.118056,   0.118056
9:  -0.655123,  -0.655123

Now if I make a new generator between the generates it works ok :

std::array<float, 1000> x;
std::array<float, 1000> y;

// Generators in different scopes, OK
std::random_device rd;
{
  std::mt19937_64 gen(rd());
  std::uniform_real_distribution<float> dis(-1.f, 1.f);
  auto rand = std::bind(dis, gen);
  std::generate(x.begin(), x.end(), rand);
}
{
  std::mt19937_64 gen(rd());
  std::uniform_real_distribution<float> dis(-1.f, 1.f);
  auto rand = std::bind(dis, gen);
  std::generate(y.begin(), y.end(), rand);
}

Gives :

0:   0.391496,   -0.64993
1:   0.429592,   0.835015
2: 0.00735116,    0.77657
3:  -0.548355, -0.0794801
4:  -0.312095,  -0.119841
5:   0.931296,   0.997449
6:  -0.934924,  -0.832223
7:   0.432267,   0.181224
8:   0.942709,   0.165024
9:   0.315852,  -0.654576

And now with the same generator using for loops, it works as well :

// Both arrays assigned in the same loop, OK
for(size_t i = 0; i < x.size(); ++i)
{
  x[i] = rand();
  y[i] = rand();
}

// Arrays in separated loops, OK
for(size_t i = 0; i < x.size(); ++i)
  x[i] = rand();

for(size_t i = 0; i < y.size(); ++i)
  y[i] = rand();

It looks like something related to std::generate, but I can't find out what would cause this behavior.

Any explanation ? Cheers

EDIT:

As pointed out by dotcavader, the issue comes simply because the generator object has been copied (both by std::bind and std::generate). Hence the generator start with the exact same internal state for both generate.

Just to complete the answer, Praetorian and Casey gave two easy solutions for this :

Using std::reference_wrapper to store a reference in the bind instead of a copy :

auto rand = std::bind(dis, std::ref(gen));

Using a lambda that returns a reference :

auto rand = [&](){ return dis(gen); };
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

I believe what's happening here is that std::generate takes its generator argument by value. As soon as you pass it in, it's copied. That means what happens inside std::generate does not affect the function object you passed in.

So you call generate again, and you copy the same generator function object, which then starts generating with exactly the same state including (somewhere inside there) which ever seed was used to initialise the number generation.

If you create a DIFFERENT generator function, even with the same parameters, you must get a different random seed inside of it. This is how your other methods are generating different results. Ultimately, their generators start with a different seed.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...