老司机的新问题,取得[min, max]范围的随机数。
C版本的rand
函数很不容易用对,直接用rand() % (max - min + 1) + min
,这个公式不对。这个公式与取最低位的算法相同,而随机数的最低几位不一定等概率。
Donald Knuth
博士教导我们正确的用法是 rand() / ((RAND_MAX + 1U) / (max - min + 1)) + min,这把rand的所有可能的值分成max - min个桶,每个数落入0号到max - 1号桶的概率相等。
C++中有std::uniform_int_distribution<>,可以直接取一定范围正态分布的随机数,我也没有深究过上面那个公式是否不正确。
结果还真的不正确。
因为大多数情况下,RAND_MAX / (max - min + 1)
都除不尽, 小数部分被舍去后,数值偏小。比如[RAND_MAX / 2, RAND_MAX / 2 + 1]
这组,如果 RAND_MAX = 11,那么[0 - 4]的rand()被认为是5,概率是5/11,而5-11被认为是6,概率是6/11。
当采样次数与RAND_MAX同数量级时,这个概率差就显示出来了。这个值有时可能只有32767。大部分平台上这个值 为INT_MAX。
做个游戏什么的,还没有问题,一但用于统计数据,这就出错了。
stackoverflow上有人给出了正确的算法,注意这个算法是[0, max]的区间。
https://stackoverflow.com/questions/2509679/how-to-generate-a-random-integer-number-from-within-a-range#6852396stackoverflow.com本质上很简单,就是把RAND_MAX除不尽的部分从rand中消去。
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 overflowwhile (num_rand - defect <= (unsigned long)x);// Truncated division is intentionalreturn x / bin_size;
}
当RAND_MAX不够大时怎么办,可以通过以下公式扩展随机数。
unsigned long newrand = rand() * ((unsigned long)RAND_MAX +1) + rand()
注意RAND_MAX的取值,不要溢出了。