LevelDB 原始碼解析之 Random 隨機數

debugzhang發表於2021-03-30

GitHub: https://github.com/storagezhang

Emai: debugzhang@163.com

華為雲社群: https://bbs.huaweicloud.com/blogs/249894

LevelDB: https://github.com/google/leveldb

C 語言中偽隨機數生成演算法實際上是採用了"線性同餘法":

\(seed = (seed * A + C ) \% M\)

其中 \(A,C,M\) 都是常數(一般會取質數)。當 \(C=0\) 時,叫做乘同餘法。

假設定義隨機數函式

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\)

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<uint32_t>((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 語句:

if (seed_ > M) {
  seed_ -= M;
}

由於 \(H\)\(L\) 都小於 \(M\),故 \(H+M<2L\)

經過語句,等式右邊也等於 \((H + L) \%\ M\) 了。

綜上,等式成立

相關文章