借助梅森旋转原理设计 mt44497

· · 科技·工程

设计想法

众所周知,C++11 标准加入了基于梅森旋转的伪随机数生成器 mt19937,而它名字里的 19937 则是梅森素数 2^{19937}-1 的指数部分,下面是截至目前已经发现的所有梅森素数。

序号 指数 p 梅森素数 M_p = 2^p - 1 位数 发现年份 发现者/验证者
1 2 2^2 - 1 = 3 1 古代 已知
2 3 2^3 - 1 = 7 1 古代 已知
3 5 2^5 - 1 = 31 2 古代 已知
4 7 2^7 - 1 = 127 3 古代 已知
5 13 2^{13} - 1 = 8191 4 1456 匿名
6 17 2^{17} - 1 = 131071 6 1588 Cataldi
7 19 2^{19} - 1 = 524287 6 1588 Cataldi
8 31 2^{31} - 1 = 2147483647 10 1772 Euler
9 61 2^{61} - 1 19 1883 Pervushin
10 89 2^{89} - 1 27 1911 Powers
11 107 2^{107} - 1 33 1914 Powers
12 127 2^{127} - 1 39 1876 Lucas
13 521 2^{521} - 1 157 1952 Robinson
14 607 2^{607} - 1 183 1952 Robinson
15 1279 2^{1279} - 1 386 1952 Robinson
16 2203 2^{2203} - 1 664 1952 Robinson
17 2281 2^{2281} - 1 687 1952 Robinson
18 3217 2^{3217} - 1 969 1957 Riesel
19 4253 2^{4253} - 1 1281 1961 Hurwitz
20 4423 2^{4423} - 1 1332 1961 Hurwitz
21 9689 2^{9689} - 1 2917 1963 Gillies
22 9941 2^{9941} - 1 2993 1963 Gillies
23 11213 2^{11213} - 1 3376 1963 Gillies
24 19937 2^{19937} - 1 6002 1971 Tuckerman
25 21701 2^{21701} - 1 6533 1978 Noll、Nickel
26 23209 2^{23209} - 1 6987 1979 Noll
27 44497 2^{44497} - 1 13395 1979 Nelson、Slowinski
28 86243 2^{86243} - 1 25962 1982 Slowinski
29 110503 2^{110503} - 1 33265 1988 Colquitt、Welsh
30 132049 2^{132049} - 1 39751 1983 Slowinski
31 216091 2^{216091} - 1 65050 1985 Slowinski
32 756839 2^{756839} - 1 227832 1992 Slowinski、Gage
33 859433 2^{859433} - 1 258716 1994 Slowinski、Gage
34 1257787 2^{1257787} - 1 378632 1996 Slowinski、Gage
35 1398269 2^{1398269} - 1 420921 1996 Armengaud
36 2976221 2^{2976221} - 1 895932 1997 Spence
37 3021377 2^{3021377} - 1 909526 1998 Clarkson
38 6972593 2^{6972593} - 1 2098960 1999 Hajratwala
39 13466917 2^{13466917} - 1 4053946 2001 Cameron
40 20996011 2^{20996011} - 1 6320430 2003 Shafer
41 24036583 2^{24036583} - 1 7235733 2004 Findley
42 25964951 2^{25964951} - 1 7816230 2005 Nowak
43 30402457 2^{30402457} - 1 9152052 2005 Cooper、Boone
44 32582657 2^{32582657} - 1 9808358 2006 Cooper、Boone
45 37156667 2^{37156667} - 1 11185272 2008 Elvenich
46 42643801 2^{42643801} - 1 12837064 2009 Strindmo
47 43112609 2^{43112609} - 1 12978189 2008 Smith
48 57885161 2^{57885161} - 1 17425170 2013 Cooper
49 74207281 2^{74207281} - 1 22338618 2016 Cooper
50 77232917 2^{77232917} - 1 23249425 2017 Pace
51 82589933 2^{82589933} - 1 24862048 2018 Patrick Laroche
52 136279841 2^{136279841} - 1 41024320 2024 Luke Durant

可以看到 2^{19937}-1 的后面还有很多梅森素数,而其中有一个 2^{44497}-1 的指数 44497 不仅没有后面那么大,而且还好记。于是很容易想到设计一个原理类似的 mt44497

思路推导

我们先看 mt19937 的原理(这样方便给出一些常数的值)。

1 内部状态

mt19937 内部有一个状态数组 state[624]624 个整形,这个数也约等于 \dfrac{19937}{32}

2 递推方式

线性递推

有以下公式:

x_{k+n}=x_{k+m} \oplus ((\operatorname{upper}(x_k) \parallel \operatorname{lower}(x_{k+1}))\cdot A)

其中,\operatorname{upper}(a)a 的第 31 位,而 \operatorname{lower}(a)a 的第 0 至第 30 位,符号 \parallel 表示位拼接,\oplus 是异或。
mt19937 中,n=624m=397

$$ (x \cdot A)=\begin{cases} \lfloor \dfrac{x}{2} \rfloor & \operatorname{LSB}(x)=0\\ \lfloor \dfrac{x}{2} \rfloor\oplus 9908B0DF_{(16)} & \operatorname{LSB}(x)=1 \end{cases} $$ 其中 $\operatorname{LSB}(x)$ 表示 $x$ 的第 $0$ 位。 #### 输出变换 对生成的 $x_{k+n}$ 进行非线性变换提高质量: $$ y=x_{k+n} \oplus \lfloor \dfrac{x_{k+n}}{2^u} \rfloor\\ y=y \oplus ((y \times 2^s) \operatorname{and} b)\\ y=y \oplus ((y \times 2^t) \operatorname{and} c)\\ y=y \oplus \lfloor \dfrac{y}{2^l} \rfloor $$ 在 `mt19937` 中,$u=11,s=7,t=15,l=18,b=9D2C5680_{(16)},c=EFC60000_{(16)}$。 $y$ 即是返回的结果,具体地,`mt19937` 在每生成 $624$ 个随机数后再统一生成 $624$ 个整数在 `state` 中。 ### 3 让我们类推一下 我们对于 `mt44497` 发现参数基本上可以照搬,只有像 $n,m$ 这样的数需要修改。 于是我们就得到了 `mt44497` 的基本代码实现。 ## 代码实现 这里使用无符号 $32$ 位整数。 ```cpp class mt44497{ public: typedef unsigned int uint32; enum{STATE_SIZE=1392,DEFAULT_SEED=5489U,MAX_VALUE=0xFFFFFFFFU}; explicit mt44497(uint32 Seed=DEFAULT_SEED){ seed(Seed); } void seed(uint32 seed=DEFAULT_SEED){ state[0]=seed; for(pos=1;pos<STATE_SIZE;++pos){ state[pos]=1812433253U*(state[pos-1]^(state[pos-1]>>30))+pos; } } uint32 operator()(){ if(pos>=STATE_SIZE){twistState();} uint32 y=state[pos++]; y^=(y>>15)|(y<<17); y^=(y>>11); y^=(y<<7)&0x9D2C5680U; y=(y&0xFFFFFF00)|((y^(y>>8))&0xFFFFFF); y^=(y<<15)&0xEFC60000U; y^=(y<<18); return y; } uint32 min()const{return 0;} uint32 max()const{return MAX_VALUE;} private: void twistState(){ const uint32 MATRIX_A=0x9908B0DFU; const uint32 UPPER_MASK=0x80000000U; const uint32 LOWER_MASK=0x7FFFFFFFU; for(int k=0;k<STATE_SIZE;++k){ uint32 x=(state[k]&UPPER_MASK)|(state[(k+1)%STATE_SIZE]&LOWER_MASK); state[k]=state[(k+397)%STATE_SIZE]^(x>>1); if(x&0x1U){ state[k]^=MATRIX_A; } } pos=0; } uint32 state[STATE_SIZE]; int pos; }; ```