LevelDB 原始碼解析之 Random 隨機數
阿新 • • 發佈:2021-03-31
> GitHub: https://github.com/storagezhang
>
> Emai: [email protected]
>
> 華為雲社群: https://bbs.huaweicloud.com/blogs/249894
>
> LevelDB: https://github.com/google/leveldb
C 語言中偽隨機數生成演算法實際上是採用了"線性同餘法":
$seed = (seed * A + C ) \% M$
其中 $A,C,M$ 都是常數(一般會取質數)。當 $C=0$ 時,叫做乘同餘法。
假設定義隨機數函式
```c++
void rand(int &seed)
{
seed = (seed * A + C ) % M;
}
```
每次呼叫 `rand` 函式都會產生一個隨機值賦值給 `seed`,實際上 `rand` 函式生成的隨機數是一個遞推序列,初值為 `seed`。所以當初始的 `seed` 相同時,得到的遞推序列也會相同。我們稱 `seed` 為隨機數種子,稱 `rand` 生成的隨機數為偽隨機數,一個偽隨機數常用的原則就是 `M` 儘可能的大。
在 LevelDB 的隨機數類 `Random` 類中,$A=16807, M=2147483647, C=0$:
```c++
explicit Random(uint32_t s) : seed_(s & 0x7fffffffu) {
// Avoid bad seeds.
if (seed_ == 0 || seed_ == 2147483647L) {
seed_ = 1;
}
}
uint32_t Next() {
static const uint32_t M = 2147483647L; // 2^31-1
static const uint64_t A = 16807; // bits 14, 8, 7, 5, 2, 1, 0
// We are computing
// seed_ = (seed_ * A) % M, where M = 2^31-1
//
// seed_ must not be zero or M, or else all subsequent computed values
// will be zero or M respectively. For all other values, seed_ will end
// up cycling through every number in [1,M-1]
uint64_t product = seed_ * A;
// Compute (product % M) using the fact that ((x << 31) % M) == x.
seed_ = static_cast((product >> 31) + (product & M));
// The first reduction may overflow by 1 bit, so we may need to
// repeat. mod == M is not possible; using > allows the faster
// sign-bit-based test.
if (seed_ > M) {
seed_ -= M;
}
return seed_;
}
```
原始碼中利用 `(product >> 31) + (product & M)` 來代替 `product % M`,主要是為了避免 64 位除法。
下面證明 $product\ \%\ M = (product >> 31) + (product\ \&\ M)$:
$$
\begin{align}
&將\ product\ 分為高\ 33\ 位和低\ 31\ 位 \\
\\
&令高\ 33\ 位的值為\ H,低\ 31\ 位的值為\ L \\
\\
&則\ product = H << 31 + L = H \cdot 2^{31}+L = H \cdot M + L \\
\\
&因為\ product = seed \cdot A, 且\ seed\ 和\ A\ 都小於\ M,故\ H\ 必小於\ M \\
\\
&等式左邊 = product \%\ M = (H \cdot M+L) \%\ M = (H + L) \%\ M \\
\\
&等式右邊 = (product >> 31) + (product\ \&\ M) = (H \cdot 2^{31}+L)>>31 + L = H + L \\
\end{align}
$$
此時考慮下方的 `if` 語句:
```c++
if (seed_ > M) {
seed_ -= M;
}
```
由於 $H$ 和 $L$ 都小於 $M$,故 $H+M<2L$。
經過語句,等式右邊也等於 $(H + L) \%\ M$ 了。
綜上,等