4

I am trying to generate randoms within a range. I have multiple objects in multiple threads that would be calling this function, the function is in a singleton class so there is only one object that is called. But I noticed that my random numbers are closer to the middle of the range and by consequence, to each other for the first 200 calls, then they start to slowly disperse over the given range.

  1. Is there a harm in re-instantiating and re-seeding every time the function called? i did read this answer but didn't fully understand it.
  2. I tried to keep std::uniform_real_distribution<double> dis as a class variable but i kept getting errors that uniform_real_distribution is not a member of std:: why is that ?
  3. I tried to keep std::shared_ptr< std::uniform_real_distribution<double> > _uniform_distribution as a class variable, and still got errors.
  4. What is the correct way to reference a random generator?
  5. What is the right solution for this problem?

double getRandom(Object* foo){  
 std::random_device rnd;
 std::mt19937 gen(rnd());
 std::uniform_real_distribution<double> dis(0, foo->limit);
 double random = dis(gen);
 return random;
}
Community
  • 1
  • 1
InsaneBot
  • 2,422
  • 2
  • 19
  • 31
  • 2
    As it stands right now, you're re-seeding your mt19937 from `random_device` every time you get another random number. That being the case, you might as well just return data directly from random_device. The usual reason to seed from random_device is to seed the mt19937 *once*, then get many numbers before seeding it again. – Jerry Coffin Mar 17 '15 at 15:23
  • Did you include `` before trying to reference `uniform_real_distribution`? – Emil Laine Mar 17 '15 at 15:25
  • See here how to seed it. Only do it once. http://stackoverflow.com/questions/15509270/does-stdmt19937-require-warmup – Neil Kirk Mar 17 '15 at 15:25
  • @zenith Yes is included – InsaneBot Mar 17 '15 at 15:25
  • 1
    2. sounds like you didn't include `` before the class definition. But you probably to want to recreate the distribution each time (since `foo->limit` can vary each time); just make `gen` a class member, seeded once when it's initialised. – Mike Seymour Mar 17 '15 at 15:26
  • @MikeSeymour exactly, but i am having trouble making it a member, can you provide an example to how 'gen' should be referenced, is there anything that has to be done to 'gen' in the class constructor? – InsaneBot Mar 17 '15 at 15:28
  • @BinaryA: I don't know what you mean by "referenced". Declare and initialise it like any other class member: `std::mt19937 gen {std::random_device()()};` (or leave the initialiser out there, and initialise it with `gen(std::random_device()())` in the constructors' list, if you prefer.) – Mike Seymour Mar 17 '15 at 15:30

4 Answers4

7

You should seed a random number generator only once. Other than that, your getRandom function is correct. As for how to use all this in a class context, here's how you could do it:

#include <random>

class RNG
{
public:
    RNG::RNG() : gen(std::random_device()()) {} // Seeds the mt19937.

    double getRandom(Object* foo){  
        std::uniform_real_distribution<double> dis(0, foo->limit);
        double random = dis(gen);
        return random;
    }

private:
    std::mt19937 gen;
};

So at the the start of the program, you instantiate one RNG, and the mt19937 will be seeded once. Then you just call getRandom on that same RNG whenever you need a random number.

Emil Laine
  • 41,598
  • 9
  • 101
  • 157
  • If you never want to reseed it and want it self-contained, you could put it static in the function. – Neil Kirk Mar 17 '15 at 15:37
  • @NeilKirk Doing so includes the assumption, that the will only be used in one context. If he uses it in a variable number of contexts, the output will change depending on the number of uses. Making it a instance variable gives him a random number generator for each use case, that does not interfere with the other generators. – scones Mar 17 '15 at 15:41
  • Hi, I'm wondering what could be the result of seeding with the same seed twice(say, 1010), would it be different than seeding once with the same number? – lorniper Oct 09 '17 at 20:54
  • @lorniper No (unless you generate random numbers between the two seeds). – Emil Laine Oct 09 '17 at 21:00
  • @emlai thanks, Indeed I generated random numbers between the two seeds, this could be detrimental to the randomness? – lorniper Oct 10 '17 at 06:31
  • No, seeding just resets the random number sequence that will be generated, so you'll get the same numbers as you did after the first seeding. – Emil Laine Oct 10 '17 at 14:35
2

If you want to make your distribution a member variable then you have to create new parameters on each invocation:

#include <random>
#include <iostream>

class Object
{
public:
    double limit = 100.0;
};

class MyRandomGen
{
    // typedef this for readability
    typedef typename std::uniform_real_distribution<double>::param_type param_type;

    std::mt19937 gen;
    std::uniform_real_distribution<double> dis;

public:
    MyRandomGen(): gen(std::random_device()()) {} // seed only once

    double getRandom(Object* foo)
    {
        param_type range {0, foo->limit}; // new distribution parameters
        return dis(gen, range);
    }
};


int main()
{
    Object o;
    MyRandomGen gen;

    for(auto i = 0; i < 10; ++i)
        std::cout << gen.getRandom(&o) << '\n';
}

Output:

48.4072
11.9905
39.0123
49.2113
69.3635
0.369986
19.9654
42.4251
92.9024
29.7522
Galik
  • 47,303
  • 4
  • 80
  • 117
0

You can make gen and rng static local variables. This will ensure, that random number generator is initialised once.

In your function generator is reinitialised during every call with new random seed.

tmp
  • 1,079
  • 9
  • 16
0

Having the distribution as a class member shouldn't be a problem. Having an instance of std::random_device as a class member will be a problem (at least in many cases) simply because it can't be copied. Fortunately, you don't really need an instance of it as a class member because (as already noted) you only want to call it once anyway. Since others have posted their ideas of a corrected generator class, I guess I'll add mine as well:

class generator {
    std::uniform_real_distribution<double> dis;
    std::mt19937 gen;
public:
    generator(double lower = 0.0, double upper = 100.0) 
        : gen(std::random_device()()), dis(lower, upper) {}

    double operator()() {
        return dis(gen);
    }
};

You could use this (for one example) like this:

generator g;

std::generate_n(std::ostream_iterator<double>(std::cout, "\n"), 20, generator());

At least in a few quick tests, I don't see any obvious central tendency in the results I get. For example:

22.832
82.3414
20.7805
28.9464
6.72104
95.8591
1.92738
70.2699
0.447961
70.591
0.549306
27.9672
10.243
23.0606
76.155
67.6821
63.7346
20.4228
77.9004
39.6607

Hitting numbers from 0.4 through 95.9 in the first 20 seems to cover the range reasonably well.

As a mostly-unrelated aside, on a 64-bit compiler you probably want to use mt19937_64 instead of mt19937, at least as a rule.

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111