7

As a complete beginner to C++, I would like to generate a random number from a normal distribution.

With the following code (derived from this post), I am able to do so:

#include <iostream>   
#include <boost/random.hpp>
#include <boost/random/normal_distribution.hpp>

using namespace std;

int main()
{
    boost::mt19937 rng(std::time(0)+getpid());
    boost::normal_distribution<> nd(0.0, 1.0);
    boost::variate_generator<boost::mt19937&,
                             boost::normal_distribution<> > rnorm(rng, nd);

    cout<< rnorm();
  return 0;
}

Since the code is quite elaborate (in my view), I thought that there might be a more straightforward solution:

#include <iostream>
#include <random>

using namespace std;

int main()
{   
    default_random_engine generator;
    normal_distribution<double> distribution(0.0,1.0);

    cout << distribution(generator);
    return 0;
}

While I can generate a random number, it is continuously the same number. That leads to two questions:

(1) Why is that happing and how do I fix this?

(2) Is there another easier way to generate random numbers?

user213544
  • 2,046
  • 3
  • 22
  • 52
  • 1) use the correct code, the one you already have? 2) it's 3 lines of code you can copy&paste from a SO answer, how can it be less difficult? Sorry I dont understand what you are asking for – 463035818_is_not_an_ai Mar 17 '20 at 11:08
  • 2
    Well I am not familiar with these functions but in C if you dont give a seed value to the rand function it will also generate the same value since the algorithm is not random but deterministic. So if you start with the same seed value (which in absence of it might be 0) it will be the same. – Eraklon Mar 17 '20 at 11:09
  • ok, so you already know what is wrong and the question is why do you get the same number on each call? – 463035818_is_not_an_ai Mar 17 '20 at 11:10
  • 1
    Thanks for all the quick replies! It is indeed my intention to understand what happens, not just to copy&past code from elsewhere. For example, I didn't know that I had to provide a seed to the generator. – user213544 Mar 17 '20 at 11:16
  • 1
    in the first version you add a seed "std::time(0)+getpid())", in the second you don't. Either way, it's pseudo-random, so for the same seed, you get the same 'random number'. In the second you could also call generator.seed(std::time(0)+getpid()); for a similar effect to the first version – Nicolae Natea Mar 17 '20 at 11:17
  • 2
    @user213544 Do you have a C++11 compiler? If so, use the standard header `` instead of ``. – Ted Lyngmo Mar 17 '20 at 11:19
  • 1
    Side note: [Why is “using namespace std;” considered bad practice?](https://stackoverflow.com/q/1452721/673852) – Ruslan Mar 17 '20 at 12:29

2 Answers2

5

Use a seed to initialize your generator. Here I am using a time-based seed.

#include <iostream>
#include <random>
#include <chrono>

using namespace std;

int main()
{
    unsigned seed = chrono::system_clock::now().time_since_epoch().count();
    default_random_engine generator(seed);
    normal_distribution<double> distribution(0.0, 1.0);

    cout << distribution(generator);
    return 0;
}
FaisalM
  • 724
  • 5
  • 18
  • Why suggest using a time based seed over a `random_device` based one? – Ted Lyngmo Mar 17 '20 at 11:10
  • 1
    @TedLyngmo corner case - MinGW's implementation of `random_device` produces the same, deterministic values, which renders it useless in such scenarios. The above solution is thus superior, but I don't know whether it's applicable somewhere else. – Fureeish Mar 17 '20 at 11:11
  • 3
    @Fureeish "_A notable implementation where std::random_device is deterministic is old versions of MinGW (bug 338, fixed since GCC 9.2), although replacement implementations exist, such as mingw-std-random_device. The latest MinGW Versions can be downloaded from GCC with the MCF thread model._" - So, instead of adapting the code to support non-conforming implementations, it's better to try to get an implementation that is conformant, and with MinGW, that's possible. :-) – Ted Lyngmo Mar 17 '20 at 11:16
  • @TedLyngmo if only everyone could use the cutting-edge implementations :> – Fureeish Mar 17 '20 at 11:20
  • @Fureeish :-) True - but MinGW isn't even mentioned in the question so I wouldn't assume OP is using a buggy version of it. – Ted Lyngmo Mar 17 '20 at 11:21
  • Are there performance differences between the boost version and this answer? Or are they negligible? – user213544 Mar 17 '20 at 12:20
  • 1
    @user213544 `default_random_engine` is implementation defined so it's hard to give a generic answer. `mt19937` in the standard is locked to a certain version of `Mersenne Twister` if I'm not mistaken. I'm not sure if `boost` uses the same implementation. There are both faster and better (when it comes to the generated data) PRNG:s than what's included in the standard though. – Ted Lyngmo Mar 17 '20 at 12:28
  • 1
    @user213544 You shouldn't worry about performance before you have performance requirements. – YSC Mar 17 '20 at 12:43
  • If you are ever needing to generate more than 1 random number during the execution, note that you only need to seed the random generator with that time based seed ONCE (typically at start up). As I like to say, you just have to "kick it in the head" one time. The purpose of a non-randomized generator at startup is to help with debugging - you would always get the same sequence of random numbers, until you "deploy", then use the time based seed. You'd think it would be the other way around (i.e. initialize with a seed of 0 in debug build if you want the same "pseudo-random" sequence). – franji1 Mar 17 '20 at 13:59
1

(1) Why is that happing and how do I fix this?

It's happening because you default construct your PRNG (pseudo random number generator) and don't seed it. A PRNG generates a deterministic sequence of numbers. The sequence is typically very long and then it starts all over again. The seed is used to set the internal state of the PRNG - its starting point so to speak. Given no seed, it'll start with the same state every time.

(2) Is there another easier way to generate random numbers?

No, not using modern standard C++ (C++11 and later).

Some notes:

  • Using a time based seed based on a one-shot sample of the clock is considered bad since you risk seeding two PRNG:s with the same value.
  • You only need one PRNG (per thread that needs to generate random numbers) in you program - and seeding a PRNG is considered costly (in terms of speed). You could therefore make the generator global so it can be used everywhere in the program. The below is a thread safe version, initialized with what is supposed to be a True (but slow) RNG, generating numbers from an entropy pool, std::random_device.
    std::mt19937& prng() {           // extern declared in a header file
        static thread_local std::mt19937 gen(std::random_device{}());
        return gen;
    }
    
  • If your random_device lacks entropy or is buggy (older versions of MinGW had a buggy implementation) you can combine random_device output with a few time based numbers (sampled some time apart) to create a std::seed_seq that you use to initialize your PRNG. The below should work with both buggy and conformant implementations to create a seed that is hard to predict:

    #include <chrono>
    #include <thread>
    
    // Create a seed_seq with 2 time based numbers and 2 random_device numbers.
    // The two sleeps are done to ensure some diff in the clock counts.
    static std::seed_seq get_seed() {
        static constexpr auto min = std::chrono::steady_clock::duration::min();
        std::random_device rd;
        std::uint_least32_t si[4];
        for(size_t s = 0; s < std::size(si);) {
            si[s++] = rd();
            std::this_thread::sleep_for(min);
            si[s++] = static_cast<std::uint_least32_t>(
                std::chrono::steady_clock::now().time_since_epoch().count());
            std::this_thread::sleep_for(min);
        }
        return {si[0], si[1], si[2], si[3]};
    }
    
    std::mt19937& prng() {           // extern declared in a header file
        static thread_local std::seed_seq seed = get_seed();
        static thread_local std::mt19937 gen(seed);
        return gen;
    }
    
Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
  • Hey Ted. Could you please look at my question about normal distribution? https://stackoverflow.com/questions/70900624 – WhoCares Jan 31 '22 at 21:17
  • @WhoCares "Good" normal distribution is a bit more complicated than I can comfortably talk about. Lemire has a form of _uniform_ distribution formula that does _well_ . I suggest looking at the publications of people in that field to get a proper answer. It ought to form a solid base at least. – Ted Lyngmo Feb 01 '22 at 00:58
  • I am sorry, who is Lemire? – WhoCares Feb 01 '22 at 09:01