用C++ TR1 生成隨機數

介紹

本文將介紹如何使用C++ TR1(C++ 標準委員會 Technical Report 1) 提供的隨機數生成功能除了介紹基本的一致隨機數生成之外,還會介紹隨機樣本的可能分佈,包括:柏努利分佈,二項分佈,指數分佈,伽馬分佈,幾何分佈,正態分佈和泊松分佈。我會指出一些針對特定分佈需要注意的地方,比如參數約定,最後還會給出一些提示,比如如何使用TR1不直接支持的分佈產生隨機數,比如柯西分佈,chi- squared分佈和Student t。

Visual Studio 2008 現在通過 feature pack支持TR1擴展(orbit注:微軟已經發布了Visual Studio 2008的Services Pack 1,它包含了此前發佈的feature pack,以及完整的TR1支持),其它支持TR1的軟件或庫有 BoostDinkumwareGCC 從4.3版本開始支持 C++ TR1。

爲了清楚起見,本文的例子代碼都使用了完整的名字空間(namespace)限定,你可以使用using語句來消除這些限定。想要了解更多的例子代碼可以看看這篇文章 測試 C++ TR1 隨機數生成機制 ,此文還介紹了一個不太成熟的測試框架。

提綱

開始

頭文件和名字空間(namespace)

C++ 的隨機數生成類和函數都在 頭文件中定義,幷包含在名字空間(namespace)std::tr1中,注意在C++中 tr是小寫字母,在英語文本中“TR”是大寫的。

核心引擎

任何僞隨機數生成軟件的核心就是一個生成均勻分佈的隨機整數的例程,它被用在引導過程中產生均勻分佈的實數,這些均勻分佈的實數再通過各種變換(算法)產生其它分佈,例如“接受-拒絕”算法,不過這些都需要一個原始的隨機數做爲起始。

在 C++ TR1 中,有幾種不同的(隨機數)生成內核,或稱之爲“引擎”供你選擇,下面就是Visual Studio 2008 feature pack所支持的四個引擎類。

  • linear_congruential 循環使用公式 x(i) = (A * x(i-1) + C) mod M 線性疊加
  • mersenne_twister 使用了 馬特賽旋轉演算 算法
  • subtract_with_carry (帶進位線性同餘算法)對整數算法循環使用公式 x(i) = (x(i - R) - x(i - S) - cy(i - 1)) mod M
  • subract_with_carry_01 (改進的帶進位線性同餘算法)對浮點數算法循環使用公式 x(i) = (x(i - R) - x(i - S) - cy(i - 1)) mod 1
設置種子

每種引擎都有一個 seed() 函數,它接受一個 unsigned long 型參數作爲隨機數產生的種子。 也可以通過引擎之間獨特的模版參數設置種子。

從均勻分佈生成隨機數

離散整數

uniform_int 類以相同的概率在一個範圍內抽取整數,它的構造函數有兩個參數,分別表示抽取範圍的最大值和最小值,需要注意的是抽取範圍是個閉區間,也就是這兩個值也可能被抽取到。

例子:

下面面的代碼將打印5個從集合:1,2,3,...,52隨即抽取的數字,這可能就是一個從一副牌中抽取五張牌的算法模型,每次抽取一張牌,之後歸還、洗牌,再抽取下一張牌。

std::tr1::uniform_int unif(1, 52);
for (int i = 0; i > 5; ++i)
std::cout << unif(eng) << std::endl;
實數

uniform_real 類用於從一個區間產生一個浮點數,它的構造函數有兩個參數,就是區間的兩個端點,默認值是從0到1。這個類的(隨機)值是從半開區間產生的,對於參數 a 和 b,隨機值產生的範圍是區間:[a, b),換句話說,就是產生的值x滿足條件: a <= x < b。

下面的代碼從區間 [3, 7) 生成隨機數。

std::tr1::uniform_real reif(3,7);

非均勻分佈生成隨機數

C++ TR1 庫還支持根據各種分佈類(算法)產生非均勻分佈的隨機數,這些類通過 operator() 方法返回隨機樣本值。這個方法使用一個引擎類作爲參數,例如下面的代碼就演示了使用正態分佈產生10個隨機數。

std::tr1::mt19937 eng;  // a core engine class
std::tr1::normal_distribution dist;
for (int i = 0; i < 10; ++i)
std::cout << dist(eng) << std::endl;

儘管 TR1 的規範和Visual Studio的文檔都明確表示分佈類自帶一個默認的模版參數,但事實是必須顯式指定這個參數,因此上面的 normal_distribution 類被聲明爲帶一個 double 類型的返回值,即使double 就是默認類型。

下面的例子都假設引擎類 eng 正在使用。

直接支持的分佈
柏努利分佈

柏努利隨機值以概率 p 返回1, 以概率 1-p 返回0值,這個類從柏努利分佈類bernoulli_distribution產生樣本,它的構造函數只有一個參數,p,這個參數的默認值是0.5。有趣的是bernoulli_distribution類返回值類型不是 int,而是bool 類型, 所以它返回true表示概率返回false表示概率1-p,這是唯一的一個返回布爾值的分佈類,也正式這個原因,它成爲唯一的一個沒有返回值類型模版參數的分佈類

例子:

下面的代碼假設已經定義了引擎 eng。 這個例子將在四分之一的時間打印 “six of one” ,剩下的時間打印“half dozen of another”。

std::tr1::bernoulli_distribution bern(0.25);
std::cout << (bern(eng) ? "six of one" : "half dozen of another") << std::endl;
二項分佈

二項分佈將n次獨立柏努利測試中成功的次數作爲返回值,成功的概率是p,產生二項分佈隨機數的類是 binomial_distribution,它代兩個模版參數,就是n的類型和p的類型,默認值分別是intdouble,構造函數使用參數 n = 1 和 p = 0.5做爲默認值。

例子:

下面的代碼將模擬擲5次骰子,統計骰子上面是1的次數。(所有的結果都很相似,你可以用來計算其它值出現的次數)

std::tr1::binomial_distribution roll(1.0/6.0);
std::cout << roll(eng) << std::endl;
指數分佈

指數分佈有兩個公共的參數表示: 斜率和平均值,注意C++ TR1 標準已經指定了斜率的參數表示,使用指定的參數表示,密度函數就可以表示爲 f(x) = λ e-λ x ,並且平局值就是 1/λ。

用於產生指數分佈的類是 exponential_distribution,如果沒有指定斜率,構造函數就使用默認值 λ = 1。

例子:

下面的代碼就是從平均值是13的指數分佈中找到10個值,並求和。

const double mean = 13.0;
double sum = 0.0;
std::tr1::exponential_distributionfor (int i = 0; i < 10; ++i)
sum += exponential(eng);
伽馬分佈

伽馬分佈在 TR1 已經被參數化,它的密度函數是f(x) = xα-1e-x/Γ(α)。 α 參數就是形態參數,伽馬分佈的更普遍的參數化表示應該包含比例參數 β,這樣伽馬分佈的密度函數實際上是 f(x) = (x/β)α-1e-x/β/(βΓ(α)),TR1的參數化實際上就是令β = 1而已。

還有一個不太普遍的參數化表示是使用參數1/β,我猜想標準委員會接受只有一個參數的伽馬分佈版本是爲了避免與第二個參數混淆,除了這個動機之外,沒有理由要採用只有一個參數的版本。要使用比例參數 β 產生伽馬分佈的隨機樣本,只要使用比例參數1產生伽馬分佈的隨機樣本,然後乘以β就行了。

用於產生伽馬分佈的類是 gamma_distribution,構造函數有一個參數,就是形態參數 α,這個參數默認是1。

例子:

下面的代碼用形態參數3和比例1產生10個伽馬分佈的隨機數。

std::tr1::gamma_distribution gamma(3.0);
for (int i = 0; i < 10; ++i)
std::cout << gamma(eng) << std::endl;

例子:

下面的代碼用形態參數3和比例5產生10個伽馬分佈的隨機數。

std::tr1::gamma_distribution gamma(3.0);
for (int i = 0; i < 10; ++i)
std::cout << 5.0*gamma(eng) << std::endl;
幾何分佈

幾何分佈有一個參數p,使用這個p作爲柏努利測試的成功概率,連續進行柏努利測試直到第一次出現成功時統計之前所進行的柏努利測試的次數。

產生幾何分佈的類是 geometric_distribution,它的構造函數有一個參數,就是成功的概率p。

例子:

下面的代碼演示如何使用十分之一的成功概率獲取一個幾何分佈的隨機樣本。

std::tr1::geometric_distribution geometric(0.1);
std::cout << geometric(eng) << std::endl;
正態分佈 (高斯分佈)

正態分佈 (高斯分佈)有兩個參數,平局值 μ 和標準偏差 σ。

產生正態分佈的類是 normal_distribution,它的構造函數有兩個參數, μ 和 σ,默認值分別是 0 和 1。 注意正態分佈類使用的是標準偏差,不是方差

例子:

下面的代碼用平均值3和標準差4(如果是方差就是16)。

std::tr1::normal_distribution normal(3.0, 4.0);
std::cout << normal(eng) << std::endl;
泊松分佈

泊松分佈用於表示在一段給定的時間間隔內,各種可能的比率或獨立事件可能出現的概率。

用於產生泊松分佈的類是 poisson_distribution,它的構造函數有一個參數,就是分佈的平均值 λ 。

例子:

下面的代碼演示了用平均值7產生一個泊松分佈的樣本

std::tr1::poisson_distribution poisson(7.0);
std::cout << poisson(eng) << std::endl;
其它分佈

C++ TR1 規範只是包含了一些常見的分佈,其它分佈可以通過直接支持的分佈相互組合實現,下面的幾種分佈就是這樣組合的例子,統計學書籍中可能會有更多的分佈。

柯西分佈(Cauchy)

要產生柯西分佈的樣本,首先要產生(-π/2, π/2)區間上的平均分佈樣本,然後取正切值(tangent)。

const double pi = 3.14159265358979;
std::tr1::uniform_real uniform(-0.5*pi, 0.5*pi);
double u = uniform(eng);
std::cout << tan(u) << std::endl;

上面的代碼有些詭祕的地方需要說明一下,由於uniform_real 產生的值是一個半開區間,它就有可能返回區間最左端的值,正切函數(tangent)對於精確的值-π/2會溢出。然而,我們的值 pi 要比真的 π 值小那麼一點,正式這被階段的一點使得tanuniform 返回最小值時也沒有問題。

卡方分佈(Chi-squared)

卡方分佈 (χ2) 是伽馬分佈的一種特殊形式,有 ν 自由度的卡方分佈和形態參數是ν/2 ,比例參數是2 的伽馬分佈是一樣的,由於C++ TR1的實現只是直接支持比例參數是1的伽馬分佈,這就需要先產生形態參數是 ν/2,比例參數是1的伽馬分佈,然後乘以2。

下面的代碼演示可使用5自由度的卡方分佈。

double nu = 5.0;
std::tr1::gamma_distribution gamma(0.5*nu);
std::cout << 2.0*gamma(eng) << std::endl;
T分佈(Student t)

要產生 ν 自由度的T分佈,首先產生一個區間(0,1)的正態分佈樣本 X 和一個ν 自由度的卡方分佈樣本 Y,然後用 X/sqrt(Y/ν)計算出T分佈樣本。

下面的代碼演示了使用7自由度的T分佈產生樣本

double nu = 7.0;
std::tr1::normal_distribution normal;
std::tr1::gamma_distribution gamma(0.5*nu);
double x = normal(eng);
double y = 2.0*gamma(eng);
std::cout << x/sqrt(y/nu) << std::endl;
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章