Search code examples
c++randomnormal-distributionstd

std::normal_distribution result for the same generator value?


I noticed a very weird behavior with GNU C++ standard library.

When using a custom generator with normal distribution, it seems the distribution might return different values even for the same generator result.

Example code

#include <iostream>
#include <string>
#include <random>

template <int MAX>
class Generator
{
public:
  long long operator()() {return (++state) % MAX;}
  long long min() {return 0;}
  long long max() {return MAX;}
  long long state = 0;
};

int main()
{
  // 3 identical generators, 2 identical distributions
  Generator<5> gen0, gen1, gen2;
  std::normal_distribution<float> dist1(10, 1), dist2(10,1);

  // Print: iteration, generator output, distributions output
  printf("i   gen       dist1      dist2\n");    
  for (int i = 0; i < 10; i++) {
    printf("%d     %lld  %10.6f %10.6f\n", i, gen0(), dist1(gen1), dist2(gen2));
    dist2.reset();
  }

}  

This produces (g++ 6.2.1, same on newer ones):

i   gen       dist1      dist2
0     1   11.295982  11.295982
1     2    9.325577  10.256990
2     3   10.256990   9.672720
3     4    8.679697   9.300377
4     0    9.672720   8.957100
5     1    9.097454  11.295982
6     2    9.300377  10.256990
7     3   10.582899   9.672720
8     4    8.957100   9.300377
9     0   10.169774   8.957100

See how the dist1 results in rows 0 and 5 are different, even though generator produces the same value 1. For dist2, they're the same, as we call reset().

Btw, the reason seems to be that the GNU C++ library generates values in pairs, and caches them between the calls, not necessarily calling the generator. This also explains why dist1 row 4 is the same as dist2 (which resets the distribution) row 2.

I searched in the documentation, but I couldn't figure out if this behavior is standard compliant. Hence, I don't know if it's a standard library bug :)

Note, from what I saw, std::normal_distribution is the only class exhibiting this behavior.


Solution

  • It's standard compliant. There is no requirement that the generator be stateless. If there were, the reset function would serve no purpose.