[C++] 在整個範圍內均勻生成隨機數


Answers

警告:不要使用rand()進行統計,模擬,密碼或任何嚴重的事情。

對於一個典型的人來說,數字看起來是隨機的,不用多說了。

請參閱@ Jefffrey的回復以獲得更好的選項,或者針對加密安全隨機數字的答案 。

一般來說,高位比低位顯示更好的分佈,所以為了簡單目的而推薦的產生範圍的隨機數的方式是:

((double) rand() / (RAND_MAX+1)) * (max-min+1) + min

注意 :確保RAND_MAX + 1不溢出(謝謝Demi)!

除法在區間[0,1)中生成一個隨機數; “拉伸”到所需的範圍。 只有當max-min + 1接近RAND_MAX時,你需要像Mark Ransom發布的“BigRand()”函數。

這也避免了由於模數而導致的一些切片問題,這可能會使你的數字更加惡化。

內置的隨機數發生器不能保證具有統計模擬所需的質量。 數字對於一個人來說是“隨機的”是可以的,但是對於一個嚴肅的應用,你應該採取一些更好的方法 - 或者至少檢查一下它的屬性(均勻分佈通常是好的,但是值往往是相關的,序列是確定性的)。 Knuth在隨機數發生器方面有很好的(如果難以閱讀的)論文,而且我最近發現LFSR是非常好的,並且由於它的屬性對你來說是簡單易行的。

Question

我需要在指定的時間間隔內生成隨機數,[max; min]。

此外,隨機數應該在整個區間內均勻分佈,而不是位於特定點。

當然,我產生為:

for(int i=0; i<6; i++)
{
    DWORD random = rand()%(max-min+1) + min;
}

在我的測試中,隨機數只在一點附近產生。

Example
min = 3604607;
max = 7654607;

生成的隨機數字:

3631594
3609293
3630000
3628441
3636376
3621404

從下面的答案:OK,RAND_MAX是32767.我在C ++ Windows平台上。 有沒有其他的方法來產生均勻分佈的隨機數字?







我想補充憤怒的鞋和peterchen的優秀的答案,並在2015年的藝術狀況簡短的概述:

一些不錯的選擇

randutils

randutils(presentation)是一個有趣的新穎事物,提供了一個簡單的接口和(聲明的)強大的隨機能力。 它的缺點是增加了對你的項目的依賴,而且是新的,它還沒有經過廣泛的測試。 無論如何,免費(MIT許可證)和標題,我認為這是值得一試。

最小的樣品:一個模子卷

#include <iostream>
#include "randutils.hpp"
int main() {
    randutils::mt19937_rng rng;
    std::cout << rng.uniform(1,6) << "\n";
}

即使對圖書館不感興趣,網站( http://www.pcg-random.org/ )也會提供許多關於隨機數生成的主題,特別是C ++庫。

Boost.Random

Boost.Random (文檔)是啟發C ++ 11的<random> ,與其共享大量的接口。 在理論上也是一種外部依賴的情況下,Boost現在已經具有“準標準”庫的地位,其隨機模塊可以被看作是優質隨機數生成的經典選擇。 它具有與C ++ 11解決方案相關的兩個優點:

  • 它更便攜,只需要C ++ 03的編譯器支持
  • random_device使用系統特定的方法來提供優質的種子

唯一的小缺點是,提供random_device的模塊不是頭文件,需要編譯並鏈接boost_random

最小的樣品:一個模子卷

#include <iostream>
#include <boost/random.hpp>
#include <boost/nondet_random.hpp>

int main() {
    boost::random::random_device                  rand_dev;
    boost::random::mt19937                        generator(rand_dev());
    boost::random::uniform_int_distribution<>     distr(1, 6);

    std::cout << distr(generator) << '\n';
}

雖然最小的樣本能夠很好地工作,但真正的程序應該使用一對改進:

  • make mt19937一個thread_local :生成器相當豐滿(> 2 KB),最好不要在堆棧上分配
  • 具有多個整數的種子mt19937 :Mersenne Twister具有較大的狀態,可以在初始化期間利用更多的熵

一些不太好的選擇

C ++ 11庫

儘管是最習慣的解決方案,但即使對於基本的需求, <random>庫也不能提供太多接口的複雜性。 缺陷在std::random_device :標準沒有要求任何最小的質量輸出(只要entropy()返回0 ),到2015年,MinGW(不是最常用的編譯器,但幾乎不是一個神秘的選擇)將始終在最小樣本上打印4

最小的樣品:一個模子卷

#include <iostream>
#include <random>
int main() {
    std::random_device                  rand_dev;
    std::mt19937                        generator(rand_dev());
    std::uniform_int_distribution<int>  distr(1, 6);

    std::cout << distr(generator) << '\n';
}

如果實現不爛,這個解決方案應該等同於Boost,並且適用相同的建議。

戈多的解決方案

最小的樣品:一個模子卷

#include <iostream>
#include <random>

int main() {
    std::cout << std::randint(1,6);
}

這是一個簡單,有效和整潔的解決方案。 只有缺陷,編譯需要一段時間 - 大約兩年時間,提供C ++ 17準時發布,實驗randint函數被批准到新的標準中。 也許到那個時候播種質量的保證也會提高。

更糟糕的是更好的解決方案

最小的樣品:一個模子卷

#include <cstdlib>
#include <ctime>
#include <iostream>

    int main() {
        std::srand(std::time(nullptr));
        std::cout << (std::rand() % 6 + 1);
    }

舊的C解決方案被認為是有害的,並有很好的理由(請參閱這裡的其他答案或這個詳細的分析 )。 它的優點是:簡單,便攜,快速和誠實,從某種意義上說,所得到的隨機數字很不體面,因此不會把它們用於嚴肅的目的。

會計巨魔解決方案

最小的樣品:一個模子卷

#include <iostream>

int main() {
    std::cout << 9;   // http://dilbert.com/strip/2001-10-25
}

雖然9是一個普通的模具有點不尋常的結果,但人們不得不佩服這個解決方案的優秀組合,這個解決方案是最快,最簡單,最容易緩存和最便攜的解決方案。 通過用4代替9,對於任何類型的龍與地下城死亡都是完美的發電機,同時仍然避免符號負荷值1,2和3。唯一的小缺點是,由於迪爾伯特會計巨魔的脾氣不好,這個程序實際上會產生未定義的行為。




這不是代碼,但這個邏輯可能會幫助你。

static double rnd(void)
{
return (1.0/(RAND_MAX+1.0)*((double)(rand())) );
}

static void InitBetterRnd(unsigned int seed)
{
register int i;
srand( seed );
for( i=0; i<POOLSIZE; i++){
pool[i]= rnd();
}
}

 static double rnd0_1(void)
 {  // This function returns a number between 0 and 1
static int i=POOLSIZE-1;
double r;

i = (int)(POOLSIZE*pool[i]);
r=pool[i];
pool[i]=rnd();
return (r);
}



就其性質而言,一小部分隨機數不一定是均勻分佈的。 畢竟,它們是隨機的。 我同意,如果一個隨機數字生成器生成的數字一直顯示為分組,那麼可能有些問題。

但請記住,隨機性不一定是統一的。

編輯:我添加了“小樣本”澄清。




如果你能夠使用Boost隨機圖書館我運氣不錯。

uniform_int應該做你想要的。




((double) rand() / (RAND_MAX+1)) * (max-min+1) + min

警告 :不要忘記,由於拉伸和可能的精度錯誤(即使RAND_MAX足夠大),你將只能夠生成均勻分佈的“箱”,而不是所有的數字[最小,最大]。

@解決方案:Bigrand

警告 :請注意,這會使位數加倍,但仍然無法在範圍內生成所有數字,也就是說,BigRand()不一定會在其範圍內生成所有數字。

信息 :只要rand()的範圍超出你的區間範圍,rand()是“uniform”,你的方法(模)就是“很好”的。 最多第一個最大 - 最小數字的錯誤是1 /(RAND_MAX + 1)。

另外,我也建議在C ++ 11中切換到新的隨機包 e,它提供了比rand()更好更多種類的實現。