Random Number Generator: Should it be used as a singleton?

筅森魡賤 提交于 2019-12-04 12:39:59

Suppose you have several variables, each of which needs to be random, independent from the others, and will be regularly reassigned with a new random value from some random generator. This happens quite often with Monte Carlo analysis, and games (although the rigor for games is much less than it is for Monte Carlo). If a perfect random number generator existed, it would be fine to use a single instantiation of it. Assign the nth pseudo random number from the generator to variable x1, the next random number to variable x2, the next to x3, and so on, eventually coming back to variable x1 on the next cycle. around. There's a problem here: Far too many PRNGs fail the independence test fail the independence test when used this way, some even fail randomness tests on individual sequences.

My approach is to use a single PRNG generator as a seed generator for a set of N instances of self-contained PRNGs. Each instance of these latter PRNGs feeds a single variable. By self-contained, I mean that the PRNG is an object, with state maintained in instance members rather than in static members or global variables. The seed generator doesn't even need to be from the same family as those other N PRNGs. It just needs to be reentrant in the case that multiple threads are simultaneously trying to use the seed generator. However, In my uses I find that it is best to set up the PRNGs before threading starts so as to guarantee repeatability. That's one run, one execution. Monte Carlo techniques typically need thousands of executions, maybe more, maybe a lot more. With Monte Carlo, repeatability is essential. So yet another a random seed generator is needed. This one seeds the seed generator used to generate the N generators for the variables.

Repeatability is important, at least in the Monte Carlo world. Suppose run number 10234 of a long Monte Carlo simulation results in some massive failure. It would be nice to see what in the world happened. It might have been a statistical fluke, it might have been a problem. The problem is that in a typical MC setup, only the bare minimum of data are recorded, just enough for computing statistics. To see what happened in run number 10234, one needs to repeat that particular case but now record everything.

You should use the same instance of your random generator class whenever the clients are interrelated and the code needs "independent" random number.

You can use different objects of your random generator class when the clients do not depend on each other and it does not matter whether they receive the same numbers or not.

Note that for testing and debugging it is very useful to be able to create the same sequence of random numbers again. Therefore you should not "randomly seed" too much.

I don't think that its increasing the randomness but it reduces the memory you need to create an object every time you want to use the random generator. If this generator doesn't have any instance specific settings you can make a singleton.

Since I use the time (ns) as seed and since this time changes this works but I am wondering whether it would not be better to use only one singular generator and e.g. to make it available as a singleton.

This is a good example when the singleton is not an anti-pattern. You could also use some kind of inversion of control.

Would this increase the random number quality ?

No. The quality depends on the algorithm that generate random numbers. How you use it is irrelevant (assuming it is used correctly).

To your edit : you could create some kind of container that holds objects of your RNG classes (or use existing containers). Something like this :

std::vector< Rng > & RngSingleton()
{
  static std::vector< Rng > allRngs( 2 );
  return allRngs;
}

struct Rng
{
  void SetSeed( const int seen );
  int GenerateNumber() const;
//...
};

// ...
RngSingleton().at(0).SetSeed( 55 );
RngSingleton().at(1).SetSeed( 55 );
//...
const auto value1 = RngSingleton().at(0).GenerateNumber;
const auto value2 = RngSingleton().at(1).GenerateNumber;

Factory pattern to the rescue. A client should never have to worry about the instantiation rules of its dependencies. It allows for swapping creation methods. And the other way around, if you decide to use a different algorithm you can swap the generator class and the clients need no refactoring. http://www.oodesign.com/factory-pattern.html

--EDIT

Added pseudocode (sorry, it's not c++, it's waaaaaay too long ago since I last worked in it)

interface PRNG{
    function generateRandomNumber():Number;
}

interface Seeder{
    function getSeed() : Number;
}

interface PRNGFactory{
    function createPRNG():PRNG;
}

class MarsagliaPRNG implements PRNG{
    constructor( seed : Number ){
        //store seed
    }

    function generateRandomNumber() : Number{
        //do your magic
    }
}

class SingletonMarsagliaPRNGFactory implements PRNGFactory{
    var seeder : Seeder;
    static var prng : PRNG;
    function createPRNG() : PRNG{
        return prng ||= new MarsagliaPRNG( seeder.getSeed() );
    }
}

class TimeSeeder implements Seeder{
    function getSeed():Number{
        return now();
    }
}

//usage:
seeder : Seeder = new TimeSeeder();
prngFactory : PRNGFactory = new SingletonMarsagliaPRNGFactory();

clientA.prng = prngFactory.createPRNG();
clientB.prng = prngFactory.createPRNG();
//both clients got the same instance.

The big advantage is now that if you want/need to change any of the implementation details, nothing has to change in the clients. You can change seeding method, RNG algorithm and the instantiation rule w/o having to touch any client anywhere.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!