发布
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