釋出
2012年1月18日 (最後更新: 2012年1月18日)

隨機數生成器

評分: 4.0/5 (1682 票)
*****

隨機數生成


讓我們深入探討一個可能很有趣的話題,即 C 標準庫提供的隨機數生成。
首先,我們為什麼需要在程式中生成隨機數?
我相信這些數字在模擬和遊戲中非常有用。

C 在 <stdlib.h> 標頭檔案中提供了隨機數生成函式 rand()。

考慮以下 C 語句

 
i = rand();


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 是


2147483647

而在我的 Visual C++ 編譯器上,RAND_MAX 是


32767

每次呼叫 rand 函式時,從 0 到 RAND_MAX 的每個數字被選中的機率(機會)是相等的。
rand 直接產生的數值範圍通常與特定應用程式所需的範圍不同。
例如:
  • 一個由計算機拋硬幣的遊戲需要兩個值,比如 0 或 1。
  • 一個有 6 個面的骰子游戲,計算機需要為玩家擲骰子以獲得 1 到 6 之間的數字。

    • 為了演示 rand,讓我們開發一個程式來模擬擲六面骰子 20 次,並列印每次擲骰子的值。函式 rand 的函式原型在 <stdlib.h> 中。
      我們使用 rand 的餘數運算子 (%) 如下:
       
      rand() %6

      生成從 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 */


      每次執行此程式時,您都會發現一個不同的序列,這裡是兩次執行的結果:

      
               4         4         3         6         6
               2         6         4         3         3
      

      
              2         6         3         4         3
              3         5         4         5         6
      

      如果您在 C/C++ 方面需要任何幫助,可以透過以下方式聯絡我:
      Twitter: _mFouad
      郵件: mfouad91@gmail.com