隨機數原理


如今大部分編譯器的隨機數算法還是線性同余算法,簡稱LCG。

線性同余算法(LCG):http://en.wikipedia.org/wiki/Linear_congruential_generator

Linear Congruential Generator (LCG) represents one of the oldest and best-known pseudorandomnumbergenerator algorithms.

The generator is defined by the recurrencerelation:

X_{n+1} \equiv \left( a X_n + c \right)~~\pmod{m}

where X is the sequence of pseudorandom values, and

 m,\, 0<m  — the "modulus"
 a,\,0 < a < m — the "multiplier"
 c,\,0 \le c < m — the "increment"
 X_0,\,0 \le X_0 < m — the "seed" or "start value"

are integer constants that specify the generator. If c = 0, the generator is often called a multiplicative congruential method, or LehmerRNG. If c ≠ 0, the generator is called a mixed congruential method.

The period of a general LCG is at most m, and for some choices of a much less than that. Provided that c is nonzero, the LCG will have a full period for all seed values if and only if:

  1. \,c and \,m are relatively prime,
  2. \,a - 1 is divisible by all prime factors of \,m,
  3. \,a - 1 is a multiple of 4 if \,m is a multiple of 4.

These three requirements are referred to as the Hull-Dobell Theorem. While LCGs are capable of producing decent pseudorandom numbers, this is extremely sensitive to the choice of the parameters cm, and a.

Historically, poor choices had led to ineffective implementations of LCGs. A particularly illustrative example of this is RANDU which was widely used in the early 1970s and led to many results which are currently being questioned because of the use of this poor LCG.

The most efficient LCGs have an m equal to a power of 2, most often m = 232 or m = 264, because this allows the modulus operation to be computed by merely truncating all but the rightmost 32 or 64 bits. 

 

大致對應的實現代碼:

static unsigned int mySeed = 1;

void mySrand(unsigned int seed) {
    mySeed = seed;
}

unsigned int myRand(){
    mySeed = mySeed * 214013 + 2531011;
    return (unsigned int)((mySeed >> 16) & 0x7fff);
}

上面的代碼中,a = 214013, c = 2531011, m = 0x1ffffffff, 在32bit系統中,由於unsigned int為4字節,不可能超過m,因此程序的取模操作省略了。

mySrand傳入的參數即為初始種子,每次調用myRand會取得一個隨機數,且調用myRand產生的隨機數取決於之前的mySeed值,mySeed一直在變,故隨機數也隨之改變。

因此,srand(unsigned int)只需要調用一次就可以了。假如每次調用rand()前都調用一次相同的srand,那么產生的隨機數必然也是一樣的。

初始種子一般這樣調用srand((unsigned int)time(NULL)),如此,不同的時間調用srand就會得到不同的隨機數。

 

更好的隨機數算法,Mersenne twister,RNG,Python和Ruby都采用它作為默認的隨機數生成算法。

梅森旋轉算法Mersenne twisterhttp://en.wikipedia.org/wiki/Mersenne_twister

 

沒搞錯的話,下面的算法是C++之父寫的(C++程序設計語言 特別版):

#include <map>
#include <iostream>
class Randint { // 均勻分布,假定32位long
    unsigned long randx;
public:
    Randint(long s = 0) { randx = s; }
    void seed(long s) { randx = s; }

    // 魔幻數選用32位long中的31位
    long abs(long x) { return x&0x7fffffff; }
    static double max() { return 2147483648.0; } // 注意:double
    long draw() { return randx = randx * 1103515245; }
    double  fdraw() { return abs(draw()) / max(); } // 在區間[0, 1]
    long operator() () { return abs(draw()); } // 在區間[0, pow(2, 31)]
};

class Urand : public Randint { // 均勻分布,區間[0:n]
    long n;
public:
    Urand(long nn) { n = nn; }
    long operator() () { long r = n * fdraw(); return (r == n) ? n-1 : r; }
};

class Erand : public Randint { // 指數分布隨機數生成器 
    long mean;
public:
    Erand(long m) { mean = m; }
    long operator() () { return -mean * long( (max() - draw()) / max() + .5); }
};

int main () {
    const int count = 10;
    Urand temp(count);
    temp.seed(time(NULL));
    std::map<int, int>bucket;
    for (int i = 0; i < 1000000; ++i) {
        bucket[temp()]++;
    }
    for (int i = 0; i < count; ++i) {    
        std::cout << bucket[i] << '\n';
    }
} 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM