[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平台上。 有沒有其他方法可以生成均勻分佈的隨機數字?




我想補充一下Angry Shoe's和peterchen的出色答案,並簡要介紹2015年的藝術狀況:

一些不錯的選擇

randutils

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

最小樣本:一個模具卷

#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 (documentation)是啟發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';
}

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

  • 使mt19937成為thread_local :生成器非常豐滿(> 2 KB),最好不要在堆棧上分配
  • 具有多個整數的seed 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函數被批准到新標準中。 也許到那個時候播種質量的保證也會提高。

worse-is-better解決方案

最小樣本:一個模具卷

#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.唯一的小缺陷是,由於迪爾伯特會計巨魔的脾氣暴躁,這個程序實際上會產生未定義的行為。




如果您擔心隨機性而不是速度問題,則應使用安全的隨機數生成方法。 有幾種方法可以做到這一點......最簡單的就是使用OpenSSL's 隨機數生成器

您也可以使用加密算法(如AES )編寫自己的程序。 通過挑選種子和IV ,然後不斷重新加密加密函數的輸出。 使用OpenSSL比較容易,但不太帥氣。




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

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);
}



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

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

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




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

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

@解決方案:Bigrand

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

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

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







Links



Tags

c++ c++   random