如何使用C语言从一定范围内生成随机整数?
如何使用C语言生成随机数?例如希望生成一段特定范围的随机数,如$[1,6]$来模拟掷骰子。
到目前为止,所有答案在数学上都是错误的。返回
rand() % N
并不统一给出[0, N)
范围内的数字,除非N
除以rand()
返回的区间长度(即是 2 的幂)。此外,我们不知道rand()
的模数是否独立:它们有可能是0, 1, 2, ...
,这是均匀的但不是很随机。唯一似乎合理的假设是rand()
给出泊松分布:任何两个相同大小的非重叠子区间均可能且独立。对于一组有限的值,这意味着均匀分布,并且还确保rand()
的值很好地分散。这意味着更改
rand()
范围的唯一正确方法是将其分为多个框;例如,如果RAND_MAX == 11
并且您想要1..6
的范围,则应将{0,1}
分配给 1,将{2,3}
分配给 2,依此类推在。这些是不相交的、大小相等的区间,因此是均匀且独立分布的。使用浮点除法的建议在数学上是合理的,但原则上存在舍入问题。也许
double
的精度足以使其工作;也许不是。我不知道,也不想弄清楚;无论如何,答案取决于系统。正确的方法是使用整数运算。也就是说,您想要如下所示的内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
#include <stdlib.h> // For random(), RAND_MAX // Assumes 0 <= max <= RAND_MAX // Returns in the closed interval [0, max] long random_at_most(long max) { unsigned long // max <= RAND_MAX < ULONG_MAX, so this is okay. num_bins = (unsigned long) max + 1, num_rand = (unsigned long) RAND_MAX + 1, bin_size = num_rand / num_bins, defect = num_rand % num_bins; long x; do { x = random(); } // This is carefully written not to overflow while (num_rand - defect <= (unsigned long)x); // Truncated division is intentional return x/bin_size; }
为了获得完全均匀的分布,循环是必要的。例如,如果给你 0 到 2 之间的随机数,而你只想要 0 到 1 之间的数字,你就继续拉,直到没有 2 为止;不难检查这是否以相同的概率给出 0 或 1。 这里使用
random()
而不是rand()
因为它具有更好的分布(如rand()
的手册页所述)。如果你想获得默认范围
[0, RAND_MAX]
之外的随机值,那么你必须做一些棘手的事情。也许最方便的方法是定义一个函数random_extended()
提取n
位(使用random_at_most()
)并返回[0, 2**n)
,然后应用random_at_most()
用random_extended()
代替random()
(并用2**n - 1
代替RAND_MAX
)来提取随机值小于2**n
,假设您有一个可以保存这样的值的数字类型。最后,当然,您可以使用min + random_at_most(max - min)
获取[min, max]
中的值,包括负值。
这段代码通过以下方式确保生成的随机数在闭区间 [0, max] 内均匀分布:
- 确定随机数的范围和数量:
num_bins
表示生成的随机数的数量,即max
的值加一,因为随机数生成器的范围是左闭右开的。num_rand
表示随机数生成器的最大值加一,表示总的可能的随机数的数量。
- 计算每个区间的大小:
bin_size
表示每个区间的大小,即将随机数的总数量除以可能的随机数的数量,以确保每个随机数的概率相等。
- 处理不能均匀分配的情况:
defect
表示随机数总数量除以可能的随机数数量的余数,用于处理不能均匀分配的情况。
- 生成随机数并检查范围:
- 使用
do-while
循环来生成随机数,并检查生成的随机数是否在指定的范围内,如果不在范围内则重新生成,直到生成的随机数在指定范围内。
- 使用
- 返回均匀分布的随机数:
- 将生成的随机数除以区间大小得到的结果,即在闭区间 [0, max] 内均匀分布的随机数。
通过以上步骤,代码确保了生成的随机数在指定范围内且分布均匀。