0

I'm trying to implement a C++ class that generates Gaussian (aka normal) random floats using an API similar to Python's Numpy random number generator:

numpy.random.normal(loc, scale)

where loc is the mean and scale is the standard deviation.

Below is my attempt.

#include <cstdio>
#include <random>
#include <ctime>


class Gaussian
{
    std::default_random_engine gen;

    public:
        Gaussian()
        {
            srand((unsigned int)time(NULL));
        }

        double get(double mean, double std)
        {
            std::normal_distribution<double> nd(mean, std);
            return nd(gen);
        }
};

The problem is that in my main() function, I generate 10 random doubles, and the sequence is always the same. I am using g++ on a Mac.

int main(int argc, char**argv)
{
    srand((unsigned int)time(NULL));

    Gaussian g;
    int N = 10;
    double mean = 50.0;
    double std = 2.0;

    for (int i = 0; i < N; i++) {
        double value = g.get(mean, std);
        printf("%f\n", value);
    }
}

// Compile: g++ main.cpp

Consistently produces over multiple invocations:

47.520528
53.224019
52.765603
48.191707
46.679143
50.151444
50.194442
49.542437
51.169795
51.069510

What is going on?

stackoverflowuser2010
  • 38,621
  • 48
  • 169
  • 217
  • I don't think you seed `std::default_random_engine`, by invoking `srand` (given that one is C++ the other C), but I may be mistaken. I think that only affects `rand`. You might want to do `std::random_device r;` and `std::default_random_engine gen(r())`, or you can do `gen.seed(time(NULL))` for C style seeding – Lala5th Sep 08 '21 at 00:17

2 Answers2

3

The std::default_random_engine gen needs to be seeded differently if you want different output. The default constructor, which is what it is constructed with in your example, will always seed with default_seed, which will always be the same.

You can supply a seed using any standard C++ method to construct a class member, such as an initializer list in your constructor:

public:
    Gaussian() : gen{ time(NULL) } 
{ }

Or you could call gen.seed() in the constructor, which would be less efficient since the generator is effectively being seeded twice, once at construction and again when seed() is called.

The exact engine used by std::default_random_engine is implementation defined, but we do know that srand() has no effect on it.* This is really the fundamental flaw of the example code: The attempt to seed the random generator with srand() has no effect.

It also worth noting the time(NULL) returns the time in seconds. If you initialize the generator multiple times in the same second, you'll get the same output. std::chrono::high_resolution_clock::now().time_since_epoch().count() might be a better choice for simple clock based seeding.

Also note that re-creating the distribution object for each individual random number is inefficient. It would be better to keep using the same object if many random numbers of the same mean and std. dev. are needed.

* This comes from C++11 §[rand.req.eng] Table 117: The default constructor "[c]reates an engine with the same initial state as all other default-constructed engines of type E." And the function call operator "[a]dvances e’s state ei to ei+1 = TA(ei) ..." This leaves no possibility for srand() to change the state of a default constructed engine nor to affect an engine's state once constructed (i.e., re-seed it).

TrentP
  • 4,240
  • 24
  • 35
  • Thank you. Shouldn't the braces be changed to parentheses in the initializer list of the constructor? Such as: `Gaussian() : gen(time(NULL) ) { ... }` – stackoverflowuser2010 Sep 08 '21 at 01:03
  • You can use either in this case. The `{}` is C++11 braced-initializer style. The rules are slightly different in a way that isn't particularly important here. – TrentP Sep 08 '21 at 01:18
  • I came up with a complete class following your suggestions. Can you please provide an opinion? https://stackoverflow.com/a/69096024/4561314 – stackoverflowuser2010 Sep 08 '21 at 01:35
-1

Thanks to the answer from TrentP, I came up with the class below.

I also found these resources useful:

https://en.cppreference.com/w/cpp/numeric/random/normal_distribution

Can I change a distribution parameters?

Should I use std::default_random_engine or should I use std::mt19937?

#include <random>

// Class for generating Gaussian random numbers.
class Gaussian
{
    std::random_device _rd{}; // For random seeding.
    std::mt19937 _gen{_rd()};
    std::normal_distribution<double> _normal_dist;

    public:
        Gaussian(double mean, double stdev)
        {
            set(mean, stdev);
        }

        // Update the Gaussian distribution parameters.
        void set(double mean, double stdev)
        {
            std::normal_distribution<double>::param_type new_params(mean, stdev);
            _normal_dist.param(new_params);
        }

        double get()
        {
            return _normal_dist(_gen);
        }
};
stackoverflowuser2010
  • 38,621
  • 48
  • 169
  • 217
  • The question was why the class was returning the same value, not to write a new class that was better (which isn't really a very good _question_). You can use `std::*_distribution::param()` to change the parameters, instead of constructing a new distribution and copying it. While the clock based seeding is ok, using `std::random_device` would generally be better. – TrentP Sep 08 '21 at 02:11
  • Thank you. This problem was more involved than I originally thought. – stackoverflowuser2010 Sep 08 '21 at 05:22