是的,它是倾斜的,除非您的 RAND_MAX 恰好是 10 的倍数。
如果你取从 0 到 RAND_MAX 的数字,并尝试将它们分成 10 堆,你真的只有三种可能:
- RAND_MAX 是 10 的倍数,堆出来的都是偶数。
- RAND_MAX 不是 10 的倍数,而且堆出来的东西不均匀。
- 你把它分成不均匀的组开始,但扔掉所有会使其不均匀的“额外”。
你很少能控制 RAND_MAX,而且它通常是一个素数。那真的只剩下 2 和 3 的可能性。
第三个选项大致如下: [编辑:经过一番思考,我修改了它以产生范围 0...(limit-1) 内的数字,以适应 C 和 C++ 中大多数事物的工作方式。这也简化了代码(一点点)。
int rand_lim(int limit) {
/* return a random number in the range [0..limit)
*/
int divisor = RAND_MAX/limit;
int retval;
do {
retval = rand() / divisor;
} while (retval == limit);
return retval;
}
对于任何质疑这种方法是否会留下一些偏差的人,我还写了一个完全不同的版本,纯粹是为了测试。这个使用了一个范围非常有限的绝对非随机生成器,因此我们可以简单地遍历范围内的每个数字。它看起来像这样:
#include <stdlib.h>
#include <stdio.h>
#define MAX 1009
int next_val() {
// just return consecutive numbers
static int v=0;
return v++;
}
int lim(int limit) {
int divisor = MAX/limit;
int retval;
do {
retval = next_val() / divisor;
} while (retval == limit);
return retval;
}
#define LIMIT 10
int main() {
// we'll allocate extra space at the end of the array:
int buckets[LIMIT+2] = {0};
int i;
for (i=0; i<MAX; i++)
++buckets[lim(LIMIT)];
// and print one beyond what *should* be generated
for (i=0; i<LIMIT+1; i++)
printf("%2d: %d\n", i, buckets[i]);
}
所以,我们从 0 到 1009 的数字开始(1009 是素数,所以它不会是我们选择的任何范围的精确倍数)。因此,我们从 1009 个数字开始,并将其分成 10 个桶。这应该在每个桶中提供 100 个,并且 9 个剩菜(可以这么说)被do
/while
循环“吃掉”。正如它现在所写的那样,它分配并打印出一个额外的桶。当我运行它时,我在每个存储桶 0..9 中得到正好 100,在存储桶 10 中得到 0。如果我注释掉do
/while
循环,我在每个存储桶 0..9 中看到 100,在存储桶 10 中看到 9。
可以肯定的是,我已经针对产生的范围(主要使用素数)和存储桶的数量使用各种其他数字重新运行了测试。到目前为止,我还不能让它为任何范围产生偏斜的结果(当然,只要启用了do
/while
循环)。
另一个细节:我在这个算法中使用除法而不是余数是有原因的。一个好的(甚至是体面的)实现是rand()
无关紧要的,但是当你使用除法将数字限制在一个范围内时,你会保留输入的高位。当您使用余数执行此操作时,您将保留输入的低位。碰巧的是,对于典型的线性同余伪随机数生成器,低位的随机性往往低于高位。一个合理的实现会丢弃一些最低有效位,从而使这无关紧要。另一方面,rand
周围有一些非常糟糕的实现,并且大多数其中,您最终通过使用除法而不是余数来获得更好的输出质量。
我还应该指出,有些生成器的作用大致相反——低位比高位更随机。至少在我的经验中,这些是相当少见的。高位更随机的那个更常见。