本文旨在簡單探索線性同余發生器的一些原理和特點,很多思路借鑒於TAOCP,如果想要深入的探索這方面的知識,建議直接閱讀原著。
一、公式化定義與線性同余序列的周期
在離散數據及其應用中,如果

那么,稱a模m同余b(或者稱模m時,a等價於b),可以記為

而線性同余式就可以這樣表示:

線性同余發生器與上面的線性同余式多少有一些關系。
2.1 公式化定義
按照The Art of Computer Programming,Volume 2[1]中3.21. The Linear Congruential Method的思路,線性同余發生器(LCG:Linear Congruential Generators )可以采用如下公式化定義:

其中:
模數m和系數a是這個公式中最重要的參數,如何合理的選擇這兩個參數決定了其產生的線性同余序列(LCS:Linear Congruential Scquence):<X>質量的優劣(<X>=X1,X2,X3..Xn...)。
常數c可以為0,也可以不為0。通常,如果c=0,那么(2)式也稱作乘法線性同余發生器(MCG:Multiplicative Congruential Generator),如果c非0,(2)式,則稱作混合線性同余發生器(Mixed Linear Congruential Generator)。
X0稱作初始值,也就是所謂的種子seed。
2.2 線性同余序列的周期
如上的線性同余發生器產生的線性同余序列必然會存在一個周期P。在TAOCP中,作者以一個練習的方式提出了這個問題(exercise 3.1-6)。以下通過一種簡單直白但不嚴謹的推理來解釋這個問題。
將上述線性同余公式抽象為一個函數f(將Xn映射為Xn+1),這個函數具有自封閉特性。不難發現,實際上存在以下已知條件:

定義兩個集合:S和T。初始狀態下,集合S包含了從0到m-1所有的m個元素,集合T是一個空集。

現模擬產生LCS的過程,以任意值X0為參數,產生第一個偽隨機數X1。其值必然屬於集合S,此時將X1從集合S移動到集合T。

以X1為參數,產生第二個偽隨機數X2=f(X1),此時,X2有可能屬於集合S,也有可能屬於集合T。
1)如果X2屬於集合S,那么此時還沒有產生一個周期;
2)如果X2屬於集合T,此處也就是剛好等於X1,那么此時一個周期產生,周期P=1。
更一般地,假設在生成X1到Xi-1過程中,每個數都是在集合S中找到的,則每個數都從集合S中移動到了集合T,此時兩個集合的狀態為:

然后生成Xi時,
1)如果Xi=f(Xi-1)在集合S中,則未能產生一個周期;
2)如果Xi=f(Xi-1)在集合T中,則一個周期產生,此時周期P<=i-1。
當然周期P也有可能等於m,也就是集合S最終為空集,集合T容納了0到m-1的所有元素,且f(Xm)=X1。
因此,從以上推理不難得知,LCS必然存在一個周期P,且P<=m。
進而不難推斷:
1)如果某LCG產生的隨機序列的周期P小於m,則選取不同的初始值X0產生的LCS可能有不同的周期。
2)如果其周期P=m,則即使選取不同的X0,產生的這些LCS具有相同的周期且必定為P。
例如,對於下面發生器:

如果以初始值X0=12產生隨機序列為:
7,6,9,0,7,6,9,0,7,6,9,0,7,6,9,0......
如果以初始值X0=13產生隨機序列則為:
8,3,8,3,8,3,8,3,8,3,8,3,8,3......
但是,對於如下的發生器:

無論選取何值為種子產生的隨機序列,其周期都是16。
二、關於參數選擇
構造一個表現良好的線性同余發生器並非易事,不但考慮其產生的隨機序列的周期、隨機分布特點,還要考慮計算的效率。
在參數的選擇方面,最關鍵的就是modulus和multiplier的選擇。
3.1 modulus選擇
modulus應該盡可能大,這樣才有可能產生較長的周期。如果計算機的字長為w位,TAOCP中推薦,應該取m=2^w,或者m=2^w+1,或者m=2^w-1,也可以取小於2^w的最大的素數,在論文TABLES OF LINEAR CONGRUENTIAL GENERATORS OF DIFFERENT SIZES AND GOOD LATTICE STRUCTURE[2] 中就采用了這種m值選取的方式。
通常而言,如果取m=2^w,則利用位運算往往會使計算過程更加方便和高效,但是會存在一個問題:產生的隨機序列中各元素的低比特位的隨機性並不是很好。
簡單的解釋是,當m=2^w時,對於一個s位的整數Z,Z模m的結果實際上就是Z的比特位中右邊的s-w位的結果。
作者在原文中用了比較形式化的描述來說明這個問題:
假設d是m的一個因子,q為某一整數,令Yn滿足如下關系:

然后進行如下變換:

不難發現由公式3-1-1實際就是一個線性同余發生器,產生的隨機序列也具有周期,但是其周期小於等於d。
這里的<Y>序列實際上對應了原線性同余序列<X>的低位字節,可以將序列<Y>理解為是將<X>的低位單獨抽取出來組成的一個序列。例如如果d=2^4,則序列<Y>的周期最大也就是16,對應了序列<X>中各個元素的低4位比特位的周期最大是16,顯然低位的隨機性並不是很好。正是由於這個原因,有些平台的實現會丟棄這些隨機性不好的低比特位,截取高比特位以取得一個比較好的隨機性效果。大多數應用場景中,低比特位並不會對最終用途產生影響,因此選取m=2^w基本能夠滿足要求,實際上很多平台也確實都是取m=2^w。
如果m取2^w+1或者m=2^w-1,則不會產生上述問題。
3.2 multiplier的選擇
multiplier的選擇推理過程比較復雜一些。一般來講,應該使LCS的周期盡量長(最長為m),然后只使用一個周期內的元素,但是周期長的序列可能並不具有很好的隨機性。
比如如下的線性同余發生器:

以初始值X0=3產生隨機序列的結果:
4,5,6,7,0,1,2,3,4,5,6,7,0,1,2,3,4,5,6,7,0,1,2,3......
可見,以上序列的隨機性表現很糟糕。
在TAOCP中證明了如下定理,按照如下方式來選定系數a可以產生最大周期為m的LCS。

以上定理表明當c不等0時(c與m互質當然就不可能等於0),有可能產生周期為m的LCS。
另一方面,當c=0時,也即:

是否也有可能產生周期為m的LCS呢?答案是必然不可能。
一個簡單的反證法:
如果c=0時,產生了周期為m的LCS,那么0必然在這個序列中,但是如果0在序列中,必然會導致LCS退化成全0的序列,因此原命題必然不成立。
從另一個角度考慮:
考慮d是m的一個因子,且Xn是d的倍數,也即Xn=kd,其中k為某整數。於是有如下推導:

由此可知,Xn+1也是d的倍數,同理,后面的Xn+2,Xn+2也是d的倍數,現在這樣的序列也不是一個號的序列。所以如果要求序列中各個元素分別與m形成互質關系,因此這樣的序列的元素個數實際就是歐拉函數對m求值,也即:

簡單總結幾點即是:
1)模數m應該盡可能大,通常至少大於2^30,為了計算效率,通常會結合計算機的字長考慮選取m的值。
2)如果m選取為2的冪,也即m=2^w,則選取的a通常應該滿足a模8等於5。
3)當參數m和a的選定比較合理時,對於c的選擇約束性不是很強烈,但要保證c與m互質。例如c可以選擇1或者11。
4)種子seed應該是隨機選取的,可以將時間戳作為種子。
因為Xn的低有效位的隨機性表現並不是很好,所以在對Xn的隨機特性比較敏感的應用場景中,應該盡量采用高比特位。事實上,更應該將值Xn/m視為0到1之間的均勻分布,而不是直接將Xn視為0到m-1之間的均勻分布。所以,如果希望產生0到k-1之間的均勻分布偽隨機數,應該采用kXn/M的方式。 在具體構造一個LCG時,不仿參考TABLES OF LINEAR CONGRUENTIAL GENERATORS OF DIFFERENT SIZES AND GOOD LATTICE STRUCTURE[2]中的表格來選取參數,該文中針對MLCG和LCG給出了若干可供選擇的參數m和a的值:針對MLCG,m取小於2^w的最大質數;針對LCG,m取2^w。
三、JDK中偽隨機數生成
很多平台都采用以上線性同余發生器實現了偽隨機數的生成機制,比如下面是常見的平台實現中對應的參數(from wiki):

代碼演示如下(from rosettacode):
package com.demo; import java.util.stream.IntStream; import static java.util.stream.IntStream.iterate; public class LinearCongruentialGenerator { final static int mask = (1 << 31) - 1; static IntStream randBSD(int seed) { return iterate(seed, s -> (s * 1_103_515_245 + 12_345) & mask).skip(1); } static IntStream randMS(int seed) { return iterate(seed, s -> (s * 214_013 + 2_531_011) & mask).skip(1) .map(i -> i >> 16); } public static void main(String[] args) { System.out.println("BSD:"); randBSD(0).limit(10).forEach(System.out::println); System.out.println("\nMS:"); randMS(0).limit(10).forEach(System.out::println); } }
java中java.util.Random類的實現中,發生器的關鍵代碼如下:
private static final long multiplier = 0x5DEECE66DL; private static final long addend = 0xBL; private static final long mask = (1L << 48) - 1; protected int next(int bits) { long oldseed, nextseed; AtomicLong seed = this.seed; do { oldseed = seed.get(); nextseed = (oldseed * multiplier + addend) & mask; } while (!seed.compareAndSet(oldseed, nextseed)); return (int)(nextseed >>> (48 - bits));//丟棄低比特位,保留高比特位 } public int nextInt() { return next(32); }
同時可以看到,上滿實現中特意對比特位進行了截取,丟棄低比特位,保留高比特位。
四、References
1、Donald Knuth,The Art of Computer Programming, Volume 2
2、TABLES OF LINEAR CONGRUENTIAL GENERATORS OF DIFFERENT SIZES AND GOOD LATTICE STRUCTURE
3、https://en.wikipedia.org/wiki/Linear_congruential_generator
完。
轉載請注明原文出處:http://www.cnblogs.com/qcblog/p/8450427.html
