老司機的新問題,取得[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#6852396?stackoverflow.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的取值,不要溢出了。