2

I've scoured stack overflow for some descriptions, explanations, and snippets of the std::normal_distribution<> variable(mean, stddev) function, and have found one instance in particular, listed below, that works quite effectively for my purpose, which is simply to be able to create and house a random number from a normal distribution given the mean and stddev.

#include <random>
...
std::mt19937 generator;
double mean = 100.0, stddev  = 15.0, example;
std::normal_distribution<double> normal(mean, stddev);
example = normal(generator);
cout << "Normal: " << example << endl;

credit - https://stackoverflow.com/a/11977979/14316685.

The one problem that I"ve run into, however, is that the results from this code become quite repetitive and predicatable over time. For example, I've repeatedly used a mean of 100 and a stddev of 15, which when run over 1000 instances almost assuredly, produces exactly one instance of both approximately 52.246 and 156.86 consistently, at least on my system.

Is there a way to manipulate this code snippet, or seed if I understand this correctly, so that it produces a variety of results that are different enough each time while still obeying the normal distribution?

As I'm still new to this function, I tried utilizing std::default_random_engine generator; in the place of std::mt19937 generator, and while it produced a different result, it had a similar problem of repetition over time.

user4581301
  • 33,082
  • 7
  • 33
  • 54
  • 6
    It will create the same sequence of numbers _every_ time. If you want different sequences, seed the generator instead of default constructing it. – Ted Lyngmo Jun 27 '23 at 21:55
  • 7
    If you run **that code** multiple times you won’t get a good distribution. After you’ve initialized the two components, just call `normal(generator)` each time you need a new value. – Pete Becker Jun 27 '23 at 21:56
  • 1
    We need more context. This isn't a "Debug me!" question, but a [mre] will still hel us see exactly what you aer doing and show you how to improve on it. – user4581301 Jun 27 '23 at 22:05

1 Answers1

10

You're default constructing the generator which means that it will be initialized with the same internal state every time you construct it.

Running this program would print the same 10 numbers every time you run it:

#include <iostream>
#include <random>

int main() {
    std::mt19937 generator;
    
    double mean = 100.0, stddev = 15.0, example;
    std::normal_distribution<double> normal(mean, stddev);

    for(int i = 0; i < 10; ++i) {
        example = normal(generator);
        std::cout << "Normal: " << example << '\n';
    }
}

If you put the instantiation of the generator in the loop, it'll print the same number every time.

What you need to do is to seed the generator (once), and you can use an instance of std::random_device to get a good seed. It picks a number from an entropy pool (if it exists - which it usually does):

int main() {
    std::mt19937 generator(std::random_device{}());
    //...

Demo


If you need access to the generator from multiple places in the program, you can make it global or pass it around by-reference. If threads are involved, you can make it thread_local if you have no other way of separating thread local data.

Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
  • Warning that I hate to still have to make: some g++ implementations of `std::random_device` were legal but deterministic and some obviously broken, providing NO randomness at all. This has been fixed for years, but the older tools are still floating around out there, bedeviling the unwary and uninitiated. – user4581301 Jun 27 '23 at 22:22
  • @user4581301 Yes, I remember that. Wasn't that in a very specific incarnation of g++ too? Like mingw-somthing-something? [Bug 85494 - implementation of random_device on mingw is useless](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=85494) – Ted Lyngmo Jun 27 '23 at 22:23
  • 1
    also note that using `random_device` like this only produces a single `int`, so OP will likely "only" see ~10**9 different sequences this way. this is unlikely to be a problem, but is tiny compared to the state space of the MT – Sam Mason Jun 27 '23 at 22:35
  • 2
    @SamMason Yes, that's indeed true. For serious seeding, one would make a `seed_seq` to properly randomize `mt19937`s **massive** state. I wonder if proposing new PRNGs would be welcomed. `mt19937` is both slower and more deterministic than many modern variants that are also extremely lightweight in comparison. – Ted Lyngmo Jun 27 '23 at 22:37
  • 1
    @TedLyngmo yup, I'm not sure why the MT is still so popular! it's relatively slow, massive state, and known statistical issues. I tend to prefer xoshiro/xoroshiro but one of the PCG variants would also be a fine choice for standardisation. – Sam Mason Jun 27 '23 at 22:46
  • Totally broken `std::random` was old mingw, but deterministic behaviour was in the codebase until at least GCC 9.1. It all depends on what you're doing and how precise/secure you need to be. `rand`, crappy as it is, really is good enough for most people and programs. For a bank's automatic teller, not a ing chance. You want the dedicated, full-paranoia crypto-class smurf for that job. – user4581301 Jun 27 '23 at 23:22
  • 2
    @SamMason The reason is it's in the Standard Library. Seems pretty straightforward to me. – sweenish Jun 28 '23 at 00:42
  • @sweenish certainly the right answer here, but I meant to refer to it being used by default across so many languages and libraries. it's slowly being replaced by better generators, but it's taking its time and I encounter a lot of people who think the MT is still the best for technical reasons (i.e. as opposed to it being popular because it's easy to find an implementation) – Sam Mason Jun 28 '23 at 12:01
  • That’s very fair. – sweenish Jun 28 '23 at 12:34