C隨機數生成器的實現分析

2021-07-23 22:32:33 字數 3343 閱讀 8676

這篇文章是為了記錄和澄清乙個由來已久的關於c語言隨機數生成器的誤解。

目前所看到的所有公開的關於c隨機數生成器的中文資料,都提到經典的線性同餘法( lcg, linear congruential generator),並認為是預設的實現方法。這個說法並不準確。以gcc為例,glibc的確實現了線性同餘法,但是實現的**塊分支在日常使用中不會執行到,線性同餘法為c語言預設隨機數生成器的說法已過時。

本文將以glibc源**為例,結合掌握的文件,做一回搬運工,總結描述一下glibc中隨機數生成器的實現。

線性同餘法,lcg(linear congruential generator),是經典的偽隨機數產生器演算法,速度快,容易理解實現。 lcg 演算法數學上基於公式:

x(n+1) = (a * x(n) + c) % m

其中,各係數為:模m, 係數a, 0 < a < m,增量c, 0 <= c < m,原始值(種子) 0 <= x(0) < m 。其中引數c, m, a比較敏感,或者說直接影響了偽隨機數產生的質量。

glibc中對lcg的實現,取a = 1103515245, c = 12345, m = 134217728,即

x(n+1) = (1103515245 * x(n) + 12345) & 2147483647

由於lcg計算簡單,極省記憶體,很適合記憶體和計算資源比較緊張的嵌入式環境。但lcg有乙個嚴重的缺陷,即產生的偽隨機數強依賴於上一次生成的隨機數,且重複週期等於隨機範圍,不能用於隨機數要求高的場合。

原因是單狀態生成器在每次rand()呼叫時不會生成完全的偽隨機數,實際做的是以偽隨機的順序遍歷(0~2^31)範圍內的數。意味著當獲取到乙個為偽隨機數時,在當前週期內不會再獲取到同乙個數,只有在經過2^31次rand()呼叫之後,才會獲取這個數(而且只會獲取到這個數)。

線性累加反饋法,即lafm(linear additive feedback method),以下是glibc使用的線性累加反饋法的流程描述。其中,2147483647 = 2^31 - 1,4294967296 = 2^32. 所有變數都是整數。 對於給定的種子常量s, 初始化序列r0...r33通過以下步驟計算:

r(0) = s

r(i) = (16807 * r(i-1) ) % 2147483647 (i = 1...30)

r(i) = r(i-31) (i = 31...33)

注意數乘16807的結果應該由足夠大的整數型別儲存,避免取模操作之前發生值溢位。r(i-1)在乘法操作已經是32位整數,r(i)計算結果確保是0到2147483646之間的正整數, 即使r(i-1)為負數。

從r34開始的偽隨機序列,通過以下的線性反饋迴圈來計算:

4. r(i) = (r(i-3) + r(i-31)) % 4294967296 (i ≥ 34)

忽略掉r0...r343序列,rand()函式輸出的偽隨機數o(i)為:

5. o(i) = r(i+344) >> 1

r(i+344)的個位數字移除,生成31位隨機數o(i)。

以下為模擬步驟1~4的**:

r[0] = seed;

for (i=1; i<31; i++)

for (i=31; i<34; i++) r[i] = r[i-31];

for (i=34; i<344; i++) r[i] = r[i-31] + r[i-3];

for (i=344; iglibc實現了以上兩種演算法。lafm生成器標記為 type1, type2, type_3 和 type4 型別,lcg 生成器標記為 type0。相比lcg,lafm生成器預先生成有很多初始狀態,消除了lcg生成器的週期性遍歷的屬性,在同乙個週期內,可以多次獲取到相同的隨機數。

為了提高隨機數生成的時間和空間效率,在計算偽隨機序列時glibc使用指標指向包含前驅隨機值的陣列,寫法與按上述公式步驟直譯的方式有所不同。

int__random_r (buf, result)

struct random_data *buf;

int32_t *result;

else

else

buf->fptr = fptr;

buf->rptr = rptr;

}  return 0;

fail:

__set_errno (einval);

return -1;

}

具體使用哪個生成器依賴於初始狀態集合,由initstate()函式生成:

int

__initstate_r (seed, arg_state, n, buf)

unsigned int seed;

char *arg_state;

size_t n;

struct random_data *buf;

int type;

if (n >= break_3)

type = n < break_4 ? type_3 : type_4;

else if (n < break_1)

else

type = n < break_2 ? type_1 : type_2;

int degree = random_poly_info.degrees[type];

int separation = random_poly_info.seps[type];

buf->rand_type = type;

buf->rand_sep = separation;

buf->rand_deg = degree;

int32_t *state = &((int32_t *) arg_state)[1]; /* first location.  */

/* must set end_ptr before srandom.  */

buf->end_ptr = &state[degree];

buf->state = state;

__srandom_r (seed, buf);

state[-1] = type_0;

if (type != type_0)

state[-1] = (buf->rptr - state) * max_types + type;

return 0;

fail:

__set_errno (einval);

return -1;

}

lcg生成器在狀態陣列(buf->state)長度為8位元組時才會使用。 狀態陣列長度更大時則會啟用lafm生成器。通常在使用rand()方法時,會使用srand()設定種子常量,這時狀態陣列預設就是128位元組, 所以實際會啟用lafm生成器。

最後,以上分析如果有偏差,很可能是作者對資料或**的理解問題,歡迎即時反饋。

隨機數生成器

標頭檔案 內容 rand,srand函式和rand max常量 rand max 在windows系統中為32767 在類unix系統中為2147483647 rand 函式返回乙個0 rand max的隨機整數 srand seed 函式 接受unsigned int 型別的引數seed,以see...

MATLAB隨機數生成器

1 rand 生成 0,1 區間上均勻分布的隨機數 基本語法 rand m,n,p 生成排列成m n p 多維向量的隨機數。如果只寫m,則生成m m矩陣 如果引數為 m,n 可以省略方括號。2 randn 生成服從標準正太分布 均值為0,方差為1 的隨機數 基本語法 randn m,n,p,解釋同1...

隨機數生成器,隨機種子

遊戲中經常要用到隨機數,但如果乙個沒有隨機種子的的生成器,就沒法重複之前的隨機數了。js的math.random就用不了隨機種子,只好自己弄了乙個,有了隨機種子,每次只要傳入相同的種子,都會得到同樣的隨機數。直接 function seededrandom seed,min,max 這是一種偽隨機數...