C++中的隨機數

歷史悠久的rand()

咱們會使用從C繼承而來的 int rand(); 函數做爲隨機數發生器,該隨機數的範圍爲[0, RAND_MAX],其中 RAND_MAX 是 <stdlib.h> 中經過宏定義的一個常量,在C和C++標準中,均爲「不低於32767的正整數」,大部分編譯器都使用了32767做爲 RAND_MAXios

若是咱們要使用它的話,須要注意的是,這個rand雖然隨機性較好,但通常的用法 rand() % RANGE 很容易形成不均勻的隨機結果,例如當 #define RAND_MAX 32767 RANGE = 1000; 時,[0, 767]範圍內的機率要比[768, 999]範圍內的機率大,這是由於隨機數的區間[0, 32767]中最後的那部分[32000, 32767],因爲缺乏了[32768, 32999],故 rand() % RANGE 落在[0, 767]的機率要更大。git

解決方式也很簡單,先將隨機結果轉化爲浮點型,再按比例縮放爲整形: int(((double)rand() / (RAND_MAX + 1)) * RANGE) ,這個方法能夠解決上面的問題,但因爲計算過程極可能形成精度損失,最終又會帶來新的不平均。
算法

爲了避免老是生成一樣的隨機序列,通常會使用 void srand(unsigned int seed); 初始化隨機數發生器,若是咱們要生成固定序列的隨機數,使用一樣的隨機數種子就好,若是須要更加的隨機一些,使用 <ctime> 中的 clock_t clock(); 是個不錯的選擇,由於它的返回值是系統時間的整型表達,精度爲毫秒,能達到比較強的隨機性數據結構

C++11新增的隨機函數

基於 int rand(); 的侷限性考慮,C++11中新增了一堆隨機數引擎、隨機數分佈器、隨機數適配器,可根據指定的規則生成機率密度函數或離散機率分佈,也就是下標爲浮點數或整數的隨機數。dom

  • 分佈器

 裏面有不少實用的隨機數分佈器,用於把原始隨機數按照指定分佈進行映射,來生成所須要的特定隨機序列:函數

  •  uniform_int_distribution 均勻分佈的隨機整型(和上面的 int rand(); 功能相似)
  •  uniform_real_distribution 均勻分佈的隨機浮點型
  •  bernoulli_distribution 隨機布爾型
  •  binomial_distribution 二項分佈的隨機整型
  •  binomial_distribution 幾何分佈的隨機整型
  •  exponential_distribution 指數分佈的隨機整型
  • ......

等等,都是統計方面經常使用的函數,同時這些分佈器也均爲模版,雖然整型和浮點型的模版不能互相通用,但整型可使用包括從 short 到 unsigned long long 在內的全部原始整型,浮點型也可使用包括 float 、 double 和 long double 等全部原始浮點型。具體完整的分佈器列表和使用方法能夠查看RandomNumberDistributionui

  • 引擎

上面的這些分佈器實際上是將原始的隨機數以定製好的分佈狀況映射而來的,而C++11也提供了不少種隨機數生成引擎,如最經常使用的 default_random_engine ,其定義爲 std::linear_congruential_engine<std::uint_fast32_t, 16807, 0, 2147483647> ,而 linear_congruential_engine 的數據結構模版爲:spa

template<
    class UIntType, // 指定數據類型
    UIntType a, 
    UIntType c, 
    UIntType m // 生成公式:x[i+1] = (a * x[i] + c) mod m
> class linear_congruential_engine;

這個公式極其簡單,並且隨機性在某些狀況下並不平均,因此像我這種強迫症可能並不知足,因而C++11也提供了不少其餘的引擎可供選擇,好比說採用了馬特賽特旋轉演算法(Mersenne Twister)的 mersenne_twister_engine :3d

template<
    class UIntType, 
    size_t w, size_t n, size_t m, size_t r, // 參數含義未知,待補充
    UIntType a, size_t u, UIntType d, size_t s,
    UIntType b, size_t t,
    UIntType c, size_t l, UIntType f
> class mersenne_twister_engine;

通常使用 mt19937 ,也就是code

std::mersenne_twister_engine<std::uint_fast32_t, 32, 624, 397, 31,
                             0x9908b0df, 11,
                             0xffffffff, 7,
                             0x9d2c5680, 15,
                             0xefc60000, 18, 1812433253>

 該算法(使用上述參數時)獲取的隨機數,週期長達219937-1,也就是說,生成了219937-1個數後,纔會開始生成如出一轍的序列,且分佈極其均勻,可算是最經常使用的隨機數生成算法之一。

另外還有64位版本的 mt19937_64 :

std::mersenne_twister_engine<std::uint_fast64_t, 64, 312, 156, 31,
                             0xb5026f5aa96619e9, 29,
                             0x5555555555555555, 17,
                             0x71d67fffeda60000, 37,
                             0xfff7eee000000000, 43, 6364136223846793005>

這種算法算是基於有限二進制字段上的矩陣線性再生,可在生成高質量隨機數的同時,保證很高的效率,通常來講能夠知足絕大部分人的須要了。

下面是這個算法的僞代碼(某呼上找的,來源未知):

 另外還有一種滯後Fibonacci類型的subtract with carry(直譯爲帶進位減法?)算法引擎 subtract_with_carry_engine :

template<
    class UIntType, 
    size_t w, size_t s, size_t r // 需知足:0<s<r,0<w
> class subtract_with_carry_engine;

下面是張來自Wikipedia的算法說明,細節就不作介紹了(由於我也不懂)

{\displaystyle x(i)=(x(i-S)-x(i-R)-cy(i-1))\ {\bmod {\ }}M}

where{\displaystyle cy(i)={\begin{cases}1,&{\text{if }}x(i-S)-x(i-R)-cy(i-1)<0\\0,&{\text{otherwise}}\end{cases}}}.

其中:

 上述引擎都是使用必定的算法生成的僞隨機數序列,而 random_device 則是生成非肯定隨機數的均勻分佈整數隨機數生成器,是使用計算機硬件來生成隨機序列(若是硬件不支持或不可用,或編譯器不支持,則生成的依舊會是僞隨機數序列)的引擎,用法與其餘隨機數引擎相似。

PS:Unix/Linux下的g++會經過調用 /dev/urandom 獲取真隨機序列,但Windows下的mingw、mingw-w6四、VS都沒實現真隨機((‵□′)) 

  • 引擎適配器

引擎適配器就是將其餘引擎或引擎適配器生成的隨機數做爲輸入,按照必定的規則,再變換生成一次,簡單地說,就是隨機數的二次生成,好比:

生成指定二進制位數的隨機數的 independent_bits_engine :

template<
    class Engine, 
    std::size_t W, // 表示指定的二進制位數,需知足:0<=W<=std::numeric_limits<UIntType>::digits(即UIntType的總位數)
    class UIntType
> class independent_bits_engine;

會隨機丟棄(可指定頻率的)原生成序列中隨機數的 discard_block_engine :

template<
    class Engine, 
    size_t P, size_t R // 每P個Engine生成的隨機數,隨機保留R個,其他丟棄,需知足:0<R<=P
> class discard_block_engine;

打亂原生成序列順序的 shuffle_order_engine :

template<
    class Engine, 
    std::size_t K // 維護一個K大小的表並預裝入K個隨機數,每次生成從表中隨機取出一個數,並把Engine生成的隨機數替換入表中
> class shuffle_order_engine;

須要特別注意的是, random_device 雖然也是一種隨機數引擎,但其並非 RandomNumberEngine ,也就是不能做爲引擎適配器中的基礎引擎。因此,若是沒法實現真隨機,那麼這貨就沒用了……

簡單的例子

下面是一段生成總大小爲1GB的隨機大數並二進制形式輸出至文件的程序,使用了 std::mt19937_64 做爲基礎引擎, std::discard_block_engine<std::mt19937_64, 20, 15> 做爲引擎適配器, std::uniform_int_distribution<unsigned> 做爲分佈器:

#include <fstream>
#include <random>
#include <iostream>

const unsigned long long DATA_SIZE = 1073741824;  // shoud be times of DATA_SINGLE_SIZE
const unsigned long long DATA_SINGLE_SIZE = 128;  // shoud be times of 8
const unsigned long long DATA_COUNT = DATA_SIZE / DATA_SINGLE_SIZE;

typedef unsigned number_type;

struct Data {
    number_type v[DATA_SINGLE_SIZE / sizeof(number_type)];
};

int main() {
    std::ofstream outfile;
    outfile.open("text_data.dat", std::ios_base::binary);
    if (outfile.is_open()){
        std::discard_block_engine<std::mt19937_64, 20, 15> generator_random_discard;
        std::uniform_int_distribution<number_type> distribution;

        Data temp;

        outfile.write((char*)&DATA_SIZE, sizeof(DATA_SIZE));
        outfile.write((char*)&DATA_SINGLE_SIZE, sizeof(DATA_SINGLE_SIZE));
        for (int i = 0; i < DATA_COUNT; i++) {
            for (auto v = std::begin(temp.v); v != std::end(temp.v); v++) {
                *v = distribution(generator_random_discard);
            }
            outfile.write((char*)&temp, sizeof(Data));
        }

        outfile.close();
    }
    else {
        std::cerr << "Can\'t create the file [test_data.dat]!" << std::endl;
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

補充

時間函數time()和clock()也有了C++11的版本,具體使用方法下次再細說,下面是一個在stackoverflow.com上的代碼:

#include <chrono>
#include <random>
#include <iostream>

int main() {
    std::mt19937 eng(std::chrono::high_resolution_clock::now()
                     .time_since_epoch().count());
    std::uniform_real_distribution<> unif;
    std::cout << unif(eng) << '\n';
}
相關文章
相關標籤/搜索