C++ 中的隨機整數是從某個範圍中抽取、無法預測其規律的一個值,你可以透過結合一個隨機位元來源(引擎)與一個將這些位元對應到自訂最小值與最大值的分配來產生它。現代且建議的做法是引入 <random> 標頭,建立一個以 std::random_device 作為種子的 std::mt19937 引擎,並透過 std::uniform_int_distribution<int>(min, max) 來取得數值;較舊的 rand() 方式仍然可用,但產生的序列品質較低且端點會有偏差。對於不需要編譯程式的任務——例如為試算表產生 ID、為調查挑選樣本,或從編號清單中選出優勝者——以瀏覽器為基礎的工具速度更快,同樣公平。

how to generate random numbers in c++
how to generate random numbers in c++

在 C++ 中取得隨機數的兩種方式

大多數開發者會選擇以下兩種做法之一。舊式做法使用 <cstdlib> 中的 rand(),並以 srand(time(0)) 作為種子,再搭配經典的取模技巧將輸出壓縮到某個範圍。它能在每個編譯器上編譯,而且容易教學,但結果並非在所有整數上均勻分布,且最大值上限為 RAND_MAX,C 標準只要求它至少為 32767。現代做法則使用 C++11 引入的 <random> 標頭,它將引擎(隨機位元的來源)與分配(輸出的形狀)分離開來。正是這樣的分離,使得 std::uniform_int_distribution 能在含頭含尾的範圍中提供真正均勻的結果。

舊式做法:rand() 與 srand()

經典的模式看起來像這樣:

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

int main() {
    std::srand(static_cast<unsigned>(std::time(0)));
    int min = 1;
    int max = 100;
    int value = min + std::rand() % (max - min + 1);
    std::cout << value << std::endl;
    return 0;
}

它簡短且易讀,因此仍然是教學內容的一部分。問題出在統計上:rand() 會回傳 [0, RAND_MAX] 範圍內的值,當 RAND_MAX + 1 無法被你的範圍大小整除時,某些輸出值出現的機率會比其他值略高。對於遊戲或學習練習來說這幾乎不會造成影響;但對於模擬、密碼學,或任何對公平性敏感的使用情境,這就是個問題。

現代做法:std::mt19937 與 uniform_int_distribution

如果你想要 C++ 中符合教科書的正確模式,就是這樣:

#include <iostream>
#include <random>

int main() {
    std::random_device rd;
    std::mt19937 engine(rd());
    std::uniform_int_distribution<int> dist(1, 100);
    int value = dist(engine);
    std::cout << value << std::endl;
    return 0;
}

這裡發生三件事。std::random_device 會在可行的情況下從作業系統讀取不具確定性的位元,因此每次執行的種子都不同。std::mt19937 是一個 32 位元的梅森旋轉演算法引擎,它會將這些種子位元轉換成一長串高品質的虛擬隨機序列。std::uniform_int_distribution<int>(1, 100) 則保證從 1 到 100 之間——含頭含尾——的每個整數出現的機率都一樣,這正是大多數人在說「介於 1 到 100 之間的隨機數」時實際想要的行爲。

一步步在 C++ 中產生隨機數

  1. 引入正確的標頭。在檔案頂端加入 #include <random>。若採用舊式做法,則改用 #include <cstdlib>
  2. 建立隨機位元來源。宣告 std::random_device rd; 以取得不具確定性的種子,接著將它包進一個引擎,例如 std::mt19937 engine(rd());。如果只是測試時需要確定性,也可以改用固定的整數作為種子。
  3. 定義你的範圍。挑選一個最小值與最大值,兩者都包含在內。例如 int min = 1;int max = 100;
  4. 建立分配器。撰寫 std::uniform_int_distribution<int> dist(min, max);。每次想要取一個值時,把引擎傳給 distint value = dist(engine);
  5. 若需要多個值就重複呼叫。再次呼叫 dist(engine)。例如要從 1 到 50 取出 10 個值的迴圈:for (int i = 0; i < 10; ++i) std::cout << dist2(engine) << " ";
  6. 決定是否允許重複。若不允許重複,將每次結果存入 std::set<int>std::unordered_set<int> 中,並持續取值直到蒐集到足夠數量的唯一值,或在合理的重試次數上限後停止。

什麼時候該跳過程式碼,改用瀏覽器工具

只是為了從 1 到 50 中挑一個數字,就編譯一個小型的 C++ 程式實在太殺雞用牛刀。對於課堂示範、抽獎活動、抽樣、QA 測試資料,或任何單純需要從已知範圍中取得公平整數的任務,使用瀏覽器內的工具會更快。Lizely 上的隨機數產生器完全在你的瀏覽器中執行,讓你輸入安全整數範圍內的最小值與最大值,選擇介於 1 到 1,000 之間的結果數量,並決定是否允許重複。所有資料都不會離開你的裝置,因此無論是用於敏感的樣本或課堂環境都很合適。

常見模式與邊界情況

有幾種模式經常出現,值得記住。若要在 [0, 1) 範圍內產生隨機的 double,請使用 std::uniform_real_distribution<double> dist(0.0, 1.0);。若要產生隨機的 bool,請使用 std::uniform_int_distribution<int> dist(0, 1); 並自行轉換。若要從 std::vector 中挑選一個元素,僅在 vec.size() 相對於引擎範圍很小時,才用 dist(engine) % vec.size() 來索引;為了安全起見,請改用 std::uniform_int_distribution<std::size_t>(0, vec.size() - 1) 來包裝它。請記得重新建立分配器或謹慎地重複使用它;隨著每次呼叫而推進狀態的是引擎,而非分配器。

種子只設定一次,不要寫在迴圈裡

新手常見的錯誤是在取值迴圈內呼叫 srand(time(0))(甚至是在迴圈內建構 std::mt19937(rd()))。由於時鐘的解析度是一秒,迴圈內的每次取值都會看到相同的種子,因而產生相同的數字。請在迴圈之前設定種子一次,並把引擎傳入。

含頭含尾範圍 vs. 半開範圍

std::uniform_int_distribution<int> 在設計上兩端都包含,這符合大多數人描述範圍的方式。較舊的 rand() % n 慣用法則是半開範圍 [0, n),會默默地把最大值捨棄掉。如果你正在移轉舊程式碼,請再次確認原本的設計是否包含上界。

比較這三種做法

做法標頭範圍形狀品質最佳用途
rand() + %<cstdlib>透過取模技巧得到 [min, max]低;端點略有偏差學習練習、暫存腳本
std::mt19937 + uniform_int_distribution<random>含頭含尾 [min, max]高;均勻正式產品程式碼、模擬、遊戲
瀏覽器型 RNG無 (JS)含頭含尾 [min, max],1–1,000 筆結果高;透過 Web Crypto 在本機執行快速抽籤、抽樣、抽獎活動、課堂

統整所有重點

如果你正在撰寫 C++,請選擇以 std::random_device 作為種子的 std::mt19937,搭配一個 std::uniform_int_distribution;這個組合簡短、可移植且統計上正確。如果你只是為了非程式設計任務需要取得單一整數或少數幾個值,請完全跳過編譯步驟,改用隨機數產生器來取得含頭含尾、且資料都留在瀏覽器內的整數。至於相關的工作流程,同樣的「引擎加上分配」模式也出現在在 MATLAB 中產生隨機數的情境中,而關於不寫程式就能挑選數字的更廣泛概覽,則收錄在如何為任何任務產生隨機數指南中。如果你的數字其實是擲骰結果,那麼骰子擲骰器工具以同樣的「全程在本機」理念,支援 d4 到 d20 的各種骰子。

值得記住的 FAQ 式提醒

在你撰寫任何隨機性程式碼之前,有三個快速提醒:std::uniform_int_distribution 使用含尾的上界,因此 dist(engine) 在法律上可能回傳你所指定的最大值;std::uniform_real_distribution 使用半開的上界,永遠不會剛好回傳 1.0,這對於「以均勻值作為除數」的邏輯至關重要;而 std::random_device 在沒有硬體來源的平台上可能會回退為確定性引擎,因此不應單獨依賴它來產生安全性相關的金鑰——在此情境下,請使用經過審核的密碼學函式庫,而非 <random>

如果你正在權衡各種選項,如何在各種場合寫出得體的讚美對此有詳細說明。

如果你正在權衡各種選項,如何在 Excel 中建立日期下拉式清單對此有詳細說明。