随机数生成
让我们深入探讨一个可能很有趣的话题,即 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