当我偶然发现以下cppreference-example滚动六面模具时,我一直在研究C11中的int rand()功能。<stdlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(void)
{
srand(time(NULL)); // use current time as seed for random generator
int random_variable = rand();
printf("Random value on [0,%d]: %d\n", RAND_MAX, random_variable);
// roll a 6-sided die 20 times
for (int n=0; n != 20; ++n) {
int x = 7;
while(x > 6)
x = 1 + rand()/((RAND_MAX + 1u)/6); // Note: 1+rand()%6 is biased
printf("%d ", x);
}
}
Run Code Online (Sandbox Code Playgroud)
特别是这部分:
[...]
while(x > 6)
x = 1 + rand()/((RAND_MAX + 1u)/6); // Note: 1+rand()%6 is biased
[...]
Run Code Online (Sandbox Code Playgroud)
问题:
为什么要加+ 1u?既然rand()是[0,RAND_MAX]我猜这样做rand()/(RAND_MAX/6) -> [0,RAND_MAX/(RAND_MAX/6)] -> [0,6]?并且由于它是整数除法(LARGE/(LARGE+small)) < 1 -> 0,加法1u使其具有所需的范围[0,5]?
在上一个问题的基础上,假设[0,5],1 + (rand()/((RAND_MAX+1u)/6))应该只经历[1,6]并且永远不会触发第二个循环?
一直在四处看看是否rand()已经返回float,但这似乎是对旧代码的巨大破坏?我想如果添加1.0f而不是1u将其设置为浮点除法,该检查是否有意义?
试图把我的头缠起来,感觉我可能会丢失一些东西。
(请注意,这并不是任何对安全性至关重要的基础,我只是在探索标准库。Ds)
该代码通过确保[1,6]中的每个可能结果都是来自完全相同数量的返回值的输出来避免偏差rand。
根据定义,rand返回int值从0到RAND_MAX。因此,有1+RAND_MAX可能返回的值。如果1+RAND_MAX不是6的倍数,则不可能将其划分为6个完全相等的整数间隔。因此,代码将其划分为尽可能大的6个相等间隔和一个奇数大小的片段间隔。然后将结果rand映射到以下间隔中:前六个间隔对应于1到6的结果,最后一个间隔被拒绝,然后代码再次尝试。
当我们除以1+RAND_MAX6时,会有商q和余数r。现在考虑以下结果rand() / q:
rand在[0,q?1]中产生一个数字时,rand() / q将为0。rand在[ q,2 q?1]中产生一个数字时,rand() / q将为1。rand在[2 q,3 q?1]中产生一个数字时,rand() / q将为2。rand在[3 q,4 q?1]中产生一个数字时,rand() / q将为3。rand在[4 q,5 q?1]中产生一个数字时,rand() / q将为4。rand在[5 q,6 q?1]中产生一个数字时,rand() / q将为5。rand产生一个数字,是6 q或更大,rand() / q将是6。请注意,在前六个时间间隔中的每个时间间隔中,确切都有q个数字。在第七个间隔中,可能的返回值在[6 q,RAND_MAX]中。该间隔包含r个数字。
这段代码通过拒绝最后一个间隔来工作:
int x = 7;
while(x > 6)
x = 1 + rand()/((RAND_MAX + 1u)/6);
Run Code Online (Sandbox Code Playgroud)
只要rand在最后一个分段间隔中产生一个数字,此代码就会拒绝它,然后重试。当rand在整个间隔之一中产生一个数字时,此代码接受它并退出(在加1后,结果x为1到6,而不是0到5)。
因此,从1到6(包括1和6)的每个输出都将映射到数量完全相等的rand值。
rand鉴于我们使用的是这样的方案,从具有最小拒绝的意义上讲,这是产生均匀分布的最佳方法。1的范围rand已分为六个尽可能大的间隔。由于剩余r小于六,因此无法使用剩余的碎片间隔,因此r的未使用值无法在的六个期望值上平均分配x。
1这不一定是rand用于生成总体[1,6]中随机数的最佳方法。例如,从一个等于32767 的单次rand调用中RAND_MAX,我们可以将该值视为从000000到411411的基数六进制数字。如果它小于400000,我们可以采用最后五个数字,每个数字均匀地分布在[0 ,5],然后向我们添加所需的[1,6]。如果它是[400000,410000),我们可以使用后四位数字。如果它在[410000,411000)中,则可以使用最后三个,依此类推。此外,可能会在多个rand调用中合并否则丢弃的信息(例如前导数字),以增加每个调用获得的平均输出数量rand。