隨機數生成
讓我們深入探討一個可能很有趣的話題,即 C 標準庫提供的隨機數生成。
首先,我們為什麼需要在程式中生成隨機數?
我相信這些數字在模擬和遊戲中非常有用。
C 在 <stdlib.h> 標頭檔案中提供了隨機數生成函式 rand()。
考慮以下 C 語句
rand 函式生成一個介於 0 和 RAND_MAX 之間的整數
(RAND_MAX 是在 <stdlib.h> 標頭檔案中定義的常量)。
標準 C 規定 RAND_MAX 的值至少為 32767,這是雙位元組(即 16 位)整數的最大值。
RAND_MAX 的值因編譯器而異,您可以透過以下程式碼輕鬆檢查您編譯器確切的 RAND_MAX 值。
1 2 3 4 5 6 7 8
|
#include <stdlib.h>
#include <stdio.h>
/* function main begins program execution */
int main() {
printf("%d", RAND_MAX);
return 0; /* indicates successful termination */
} /* end main */
|
在我的 GNU C 編譯器上,RAND_MAX 是
而在我的 Visual C++ 編譯器上,RAND_MAX 是
每次呼叫 rand 函式時,從 0 到 RAND_MAX 的每個數字被選中的機率(機會)是相等的。
rand 直接產生的數值範圍通常與特定應用程式所需的範圍不同。
例如:
- 一個由計算機拋硬幣的遊戲需要兩個值,比如 0 或 1。
- 一個有 6 個面的骰子游戲,計算機需要為玩家擲骰子以獲得 1 到 6 之間的數字。
為了演示 rand,讓我們開發一個程式來模擬擲六面骰子 20 次,並列印每次擲骰子的值。函式 rand 的函式原型在 <stdlib.h> 中。
我們使用 rand 的餘數運算子 (%) 如下:
生成從 0 到 5 的整數,這稱為**縮放**,數字 6 稱為**縮放因子**。
然後,我們透過將前面的結果加 1 來**移位**生成的數字範圍。
這是完整的程式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
#include <stdio.h>
#include <stdlib.h>
/* function main begins program execution */
int main(void) {
int i; /* counter */
/* loop 20 times */
for (i = 1; i <= 20; i++) {
/* pick random number from 1 to 6 and output it */
printf("%d ", 1 + (rand() % 6));
/* if counter is divisible by 5, begin new line of output */
if (i % 5 == 0) {
printf("\n");
} /* end if */
} /* end for */
return 0; /* indicates successful termination */
} /* end main */
|
這些數字的輸出因編譯器而異,請記住,它們應該是**隨機**的,但這是我得到的輸出:
2 5 4 2 6
2 5 1 4 2
3 2 3 2 6
5 1 1 5 5
|
為了表明這些數字出現的可能性大致相等,讓我們使用上面的程式模擬擲骰子 6000 次,因此我們應該說從 1 到 6 的每個數字應該出現大約 1000 次。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
|
#include <stdio.h>
#include <stdlib.h>
/* function main begins program execution */
int main(void) {
int frequency1 = 0; /* rolled 1 counter */
int frequency2 = 0; /* rolled 2 counter */
int frequency3 = 0; /* rolled 3 counter */
int frequency4 = 0; /* rolled 4 counter */
int frequency5 = 0; /* rolled 5 counter */
int frequency6 = 0; /* rolled 6 counter */
int roll; /* roll counter, value 1 to 6000 */
int face; /* represents one roll of the die, value 1 to 6 */
/* loop 6000 times and summarize results */
for (roll = 1; roll <= 6000; roll++) {
face = 1 + rand() % 6; /* random number from 1 to 6 */
/* determine face value and increment appropriate counter */
switch (face) {
case 1: /* rolled 1 */
++frequency1;
break;
case 2: /* rolled 2 */
++frequency2;
break;
case 3: /* rolled 3 */
++frequency3;
break;
case 4: /* rolled 4 */
++frequency4;
break;
case 5: /* rolled 5 */
++frequency5;
break;
case 6: /* rolled 6 */
++frequency6;
break; /* optional */
} /* end switch */
} /* end for */
/* display results in tabular format */
printf("%s%13s\n", "Face", "Frequency");
printf("1%13d\n", frequency1);
printf("2%13d\n", frequency2);
printf("3%13d\n", frequency3);
printf("4%13d\n", frequency4);
printf("5%13d\n", frequency5);
printf("6%13d\n", frequency6);
return 0; /* indicates successful termination */
} /* end main */
|
Face Frequency
1 980
2 993
3 1030
4 1009
5 1002
6 986
|
當然,我可以使用一個包含骰子 6 個面的陣列來使程式碼更小、更優雅,但我正試圖使程式碼儘可能簡單,以適應初學者 C 程式設計師。
所以我們看到每個面被選擇了將近 1000 次。
請注意,上述程式存在一個問題,那就是如果您再次執行上述任何一個程式,您會發現它會產生相同的數字,我將在下一節中解釋這一點。
rand 函式實際上生成的是偽隨機數。反覆呼叫 rand
會產生一個看似隨機的數字序列。
然而,這個序列在每次程式執行時都會重複,這可以幫助您除錯使用 rand 函式的程式。
一旦程式經過徹底除錯,就可以將其配置為在每次執行時產生不同的隨機數序列。
這稱為隨機化,可以使用標準庫函式 **srand** 來實現。
srand 函式接受一個無符號整數作為引數,併為 rand 函式播種,以便在每次程式執行時生成不同的隨機數序列。
我將在下一個示例程式碼中解釋如何使用 srand 函式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
|
#include <stdlib.h>
#include <stdio.h>
/* function main begins program execution */
int main(void) {
int i; /* counter */
unsigned seed; /* number used to seed random number generator */
printf("Enter seed: ");
scanf("%u", &seed); /* note %u for unsigned */
srand(seed); /* seed random number generator */
/* loop 10 times */
for (i = 1; i <= 10; i++) {
/* pick a random number from 1 to 6 and output it */
printf("%10d", 1 + (rand() % 6));
/* if counter is divisible by 5, begin a new line of output */
if (i % 5 == 0) {
printf("\n");
} /* end if */
} /* end for */
return 0; /* indicates successful termination */
} /* end main */
|
這是程式的三次不同執行結果
Enter seed:3
1 3 1 2 6
4 3 2 2 1
|
Enter seed:200
2 1 5 6 1
2 2 5 3 5
|
Enter seed:3
1 3 1 2 6
4 3 2 2 1
|
請注意,當我最後一次執行時再次輸入數字 3 時,它生成的數字與第一次執行相同,因為種子值相等。
現在,如果我們想使用種子進行隨機化,但又不想每次執行程式時都輸入種子,我們可以這樣寫:
srand( time( NULL ) );
這會導致計算機讀取其時鐘以自動獲取種子值。
time 函式返回自 1970 年 1 月 1 日午夜以來經過的秒數。此值被轉換為無符號整數並用作隨機數生成器的種子。
time 函式以 NULL 作為引數,它在 time.h 標頭檔案中。
現在是時候在我們的擲骰子程式中進行最後一步了,即在不輸入種子的情況下隨機化數字。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
/* function main begins program execution */
int main(void) {
int i; /* counter */
srand(time(NULL)); /* seed random number generator */
/* loop 10 times */
for (i = 1; i <= 10; i++) {
/* pick a random number from 1 to 6 and output it */
printf("%10d", 1 + (rand() % 6));
/* if counter is divisible by 5, begin a new line of output */
if (i % 5 == 0) {
printf("\n");
} /* end if */
} /* end for */
return 0; /* indicates successful termination */
} /* end main */
|
每次執行此程式時,您都會發現一個不同的序列,這裡是兩次執行的結果:
如果您在 C/C++ 方面需要任何幫助,可以透過以下方式聯絡我:
Twitter: _mFouad
郵件: mfouad91@gmail.com