线性同余法生成伪随机数

SICP中1.2.6素数检验一节中采用概率算法,通过随机抽样的方法利用费马小定理测试来检验给出的整数是否为素数。这里需要用到随机数生成的方法(random n),即:随机返回0到n之间的任意整数,而我用的Calysto Scheme Kernel恰好没有相应的随机数生成方法的实现。之前有遇到Matlab进行随机模拟的时候,由于没有设定seed,导致运行了很久的程序一直在周期性地重复固定的“随机数”,刚好借此机会研究一下随机数生成的原理及方法。

计算机生成随机数的方法一般是采用数学法,即根据某一(递推)公式产生一个周期性足够大的数列,满足一定的均匀分布的特性,其优点在于可以迅速产生大量伪随机数,缺点是所产生的并非真正的随机数,只是近似随机。不同的公式能够产生性质不同的伪随机数(列),一种简单常用的方法称为线性同余发生器(Linear Congruence Generator, LCG),其公式如下:

$$ \begin{cases} X_0 = SEED, & \text{设定初始值}\\X_n = (A * X_{n-1} + B) (Mod M)\\ R_n = X_n/M \end{cases} $$

显然LCG方法产生的随机数列周期小于$M$,同时在保证周期尽量大的情况下,还需要适时地重设初始值,一般以系统时间作为“种子”设定初始值。Scheme的实现如下,假设将常量设定为 $A = 3, B = 0, M = 5$:

;; random.scm
(define SEED 1)
(define (seed i) (set! SEED i))
(define A 3)
(define B 0)
(define M 5)

(define (LCG)
  (begin
   (seed (remainder (+ (* A SEED) B) M))
      SEED))
(define (random n)
  (round (* (/ (LCG) (- M 1)) n) ))

将初始值设定为系统时间后,检验10次$[0, 100]$随机数产生结果:

;; test your random
(define (test-rands n)
  (if (= n 0)
      (display "Done!")
      (begin
       (display (random 100))
       (newline)
       (test-rands (- n 1)))))
(seed (round (current-time)))
(test-rands 10)

;; 75
;; 100
;; 50
;; 25
;; 75
;; 100
;; 50
;; 25
;; 75
;; 100
;; Done!

可以发现,在$A = 3, B = 0, M = 5$的条件下,LCG产生的随机数列周期仅为4,若要得到最大周期,需要满足:

  1. $B, M$互质;
  2. $M$的所有质因数都能整除$A-1$;
  3. 若$M$是4的倍数,$A-1$也是;
  4. $A,B,X_0$都比$M$小;
  5. $A,B$是正整数。

参考