[英]Why is this random number generator generating same numbers?
第一個起作用,但是第二個總是返回相同的值。 為什么會發生這種情況,我應該如何解決呢?
int main() {
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_real_distribution<> dis(0, 1);
for(int i = 0; i < 10; i++) {
std::cout << dis(gen) << std::endl;
}return 0;
}
一個不起作用:
double generateRandomNumber() {
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_real_distribution<> dis(0, 1);
return dis(gen);
}
int main() {
for(int i = 0; i < 10; i++) {
std::cout << generateRandomNumber() << std::endl;
}return 0;
}
您在哪個平台上工作? 如果不存在用於生成隨機數的硬件或操作系統功能,則允許std::random_device
為偽RNG。 它可能使用當前時間進行初始化,在這種情況下,您所調用的時間間隔可能相隔太近,以至於“當前時間”無法采用其他值。
但是,正如評論中提到的那樣,並不意味着要使用這種方式。 一個簡單的解決方法是將rd
和gen
聲明為static
。 適當的解決方法是將RNG的初始化移出需要隨機數的函數,因此也可以將其用於需要隨機數的其他函數。
第一個為所有數字使用相同的生成器,第二個為每個數字創建一個新的生成器。
讓我們比較一下這兩種情況之間的差異,看看為什么會發生這種情況。
情況1:
int main() { std::random_device rd; std::mt19937 gen(rd()); std::uniform_real_distribution<> dis(0, 1); for(int i = 0; i < 10; i++) { std::cout << dis(gen) << std::endl; }return 0; }
在您的第一種情況下,程序將執行main函數,並且在此發生的第一件事是您在堆棧上創建了一個std::random_device
, std::mt19337
和std::uniform_real_distribution<>
的實例,這些實例屬於main()
的范圍。 您的mersenne twister gen
將根據隨機設備rd
的結果進行一次初始化。 您還初始化了分布dis
以使其值的范圍為0
到1
。 每次運行該應用程序時,它們僅存在一次。
現在,您創建一個for循環,該循環從索引0
開始並遞增到9
並且在每次迭代中,您都將使用分配dis
的operator()()
將已播種的gen
傳遞給它,從而將結果顯示給cout
。 每次在該循環上, dis(gen)
都會產生不同的值,因為gen
已經僅被播種了一次。
情況2:
double generateRandomNumber() { std::random_device rd; std::mt19937 gen(rd()); std::uniform_real_distribution<> dis(0, 1); return dis(gen); } int main() { for(int i = 0; i < 10; i++) { std::cout << generateRandomNumber() << std::endl; }return 0; }
在此版本的代碼中,讓我們看一下相似之處和不同之處。 在這里,程序執行並進入main()
函數。 這次,它遇到的第一件事是從0
到9
的for循環,類似於上面的main,但是此循環是main堆棧上的第一件事。 然后調用cout
來顯示user defined function
generateRandomNumber()
。 該函數總共被調用10
次,每次您遍歷for循環時,此函數都有其自己的堆棧存儲器,該堆棧存儲器將被纏繞,展開或創建和銷毀。
現在,讓我們跳入這個名為generateRandomNumber()
user defined function
。
該代碼看起來與之前直接位於main()
的代碼幾乎完全一樣,但是這些變量位於generateRandomNumber()
的堆棧中,並具有其作用域的壽命。 每當此函數進入和超出范圍時,將創建和銷毀這些變量。 這里的另一個區別是此函數還返回dis(gen)
。
注意:我不確定
100%
是否會返回copy
或者編譯器最終是否會進行某種優化,但是按值返回通常會生成副本。
最終,當函數generateRandomNumber()
返回並且在它完全超出作用域之前,在其中std::uniform_real_distribrution<>
的operator()()
被調用,並且進入返回到自己的堆棧和作用域,然后返回主generateRandomNumber()
進行簡短介紹,然后返回主目錄。
-可視化差異-
如您所見,這兩個程序是完全不同的,確切地說是非常不同的。 如果您想獲得更直觀的證據來證明它們是不同的,可以使用任何可用的在線編譯器將每個程序輸入到它在assembly
向您顯示該程序的位置,並比較兩個匯編版本以查看它們的最終區別。
可視化這兩個程序之間差異的另一種方法不僅是查看它們的assembly
等效項,還包括使用debugger
逐行瀏覽每個程序,並關注stack calls
以及它們的纏繞和展開,並關注所有值初始化,返回和銷毀。
-評估與推理-
第一個按預期工作的原因是因為您的random device
, generator
和您的distribution
都具有main
生命周期,並且您的generator
僅使用隨機設備播種一次,並且每次僅使用一個發行版for循環。
在您的第二個版本中,main對此一無所知,它只知道它正在經歷for循環並將返回的數據從用戶函數發送到cout。 現在,每當它通過for循環時,都會調用此函數,並且每次創建和銷毀它時都按我說的那樣堆棧,因此所有其變量都將被創建和銷毀。 因此,在此實例中,您將創建並銷毀10:
rd
, gen(rd())
和dis(0,1)
實例。
-結論-
除了我上面已經描述的以外,還有更多的內容,而與您的隨機數生成器的行為有關的另一部分是用戶Kane
在對您的問題的評論中對您的陳述中提到的內容:
來自en.cppreference.com/w/cpp/numeric/random/random_device :“ std :: random_device可以根據實現定義的偽隨機數引擎來實現。在這種情況下,每個std ::: random_device對象可以生成相同的數字序列。”
每次創建和銷毀時,都會使用新的random_device
為generator
種子,但是,如果您的特定機器或操作系統不支持使用random_device
,則可以最終使用某個任意值作為其種子值,或者可以最終使用系統時鍾生成種子值。
因此,可以說它確實使用系統時鍾結束, main()
的for循環的執行發生得如此之快,以至於10
調用generateRandomNumber()
所完成的所有工作已經在幾毫秒之前完成了。通過了。 因此,此處的增量時間最小且可忽略不計,因為它在每次通過時都生成相同的種子值,並且從分布中生成相同的值。
請注意, std::mt19937 gen(rd())
非常有問題。 看到這個問題 ,它說:
此外, random_device
的用於生成“不確定性”隨機數的方法是“實現定義的”,並且random_device
如果實現由於“實現限制”而無法生成“不確定性”隨機數, random_device
允許實現“使用隨機數引擎”。 ([rand.device])。 (例如,在C ++標准下,實現可能使用來自系統時鍾的時間戳或使用快速移動的周期計數器來實現random_device
,因為兩者都是不確定的。)
應用程序不應盲目調用random_device
的生成器( rd()
),而至少也不要調用entropy()
方法,該方法以位為單位對實現的熵進行估算。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.