天天看點

C++随機數解析深度解讀如何生成大範圍随機數

深度解讀如何生成大範圍随機數

前幾天在做畢業設計的時候需要用到随機數,發現随機數并不“随機”。Rand生成的都是僞随機數。

随機數介紹

先來看看rand的函數原型及用法

#include <stdlib.h> 

int rand( void );

功能: 函數傳回一個在零到RAND_MAX 之間的僞随機整數。

例如:

srand( time(NULL) );   

for( i = 0; i < 10; i++ )     

printf( "Random number#%d: %d\n", i, rand() );

RAND_MAX在不同的平台下定義的大小不一樣 windows 定義的為0X7FFF。

與rand()配套使用的還有另外一個函數 srand()。

#include <stdlib.h>

void srand( unsigned seed );

功能: 設定rand()随機序列種子。對于給定的種子seed, rand()會反複産生特定的随機序列。    

srand( time(NULL) );   

for( i = 0; i < 10; i++ )    

printf( "Random number#%d: %d\n", i, rand() );

解釋:

srand就是為rand産生一個初始值,若不手動調用srand系統也會自動調用srand(1),這樣每次産生的随機數就都一樣了。程式第一次執行“随機數”産生順序為5,8,2,5,0,1…,第二次執行仍然是5,8,2,5,0,1…,第三次執行仍然是5,8,2,5,0,1…若我們把時間做為種子的話每次生成的“随機數”就不一樣了。

在整個程式中srand()隻需要運作一次就夠了,沒有必要在每次運作的時候都要調用,若調用的時間間隔太近的話會造成設定的種子seed全部一樣導緻産生的随機數也都一樣。

基于以上幾個特性我們稱rand産生的随機數為“僞随機數”。

随機數數内部是如何實作的

rand的内部實作是用線性同餘法做的,他不是真的随機數,隻不過是因為其周期特别長,是以有一定的範圍裡可看成是随機的,rand()會傳回一随機數值,範圍在0至RAND_MAX間。

這些是數學上的内容我們不做讨論。

如何産生大範圍的随機數

rand()函數産生的随機數範圍比較小隻能是0x0000~0x7FFF之間的整數随機數即2^15以内的數,我們在實際的應用中經常需要用到大範圍的随機數例如會用到2^32 以内的随機數。顯然rand已經無法滿足我們的要求了。

這時候就要想辦法對随機數做擴充,C++标準庫提供了标準的擴充方法RAND_MAX*rand() + rand();這樣就在[0, RAND_MAX*RAND_MAX)做到了随機。這種方法有缺點就是容易産生數的“兩端聚集”現象。但是rand*rand和rand+rand的方法做不到随機數的擴充隻會使得出的随機數産生“二次聚集”現象。

有沒有一種更好的擴充方法呢?

有利用計算機的位偏移和位運算可以實作随機數的更好擴充。

unsigned long ulrand(void)

{

    return (

     (((unsignedlong)rand()<<24)&0xFF000000ul)

    |(((unsignedlong)rand()<<12)&0x00FFF000ul)

    |(((unsigned long)rand()    )&0x00000FFFul));

}

關于位運算我就不做過多的解釋。需要注意的一點是rand傳回的随機數最高位始終為0,并不随機,是以不能參與随機數擴充的二進制運算。

負随機數

rand()-RAND_MAX/2這樣就産生了[-RAND_MAX,RAND_MAX]之間的随機數。負随機數的擴充同上。

随機小數

rand()/RAND_MAX 産生[0,1)之間的小數。

rand()/(RAND_MAX-1) 産生[0,1]之間的小數。

       倍率* 和 +起始值,可以合成我們想要的随機數。

随機數終極利器Boost随機庫

rand函數産生的随機數随機性并不夠強,範圍不夠大。我們要找另外一種更加強大的随機數數生成函數。

Boost随機庫中利用了MersenneTwister算法,可以快速産生高品質的僞随機數Mersenne Twister有以下優點:随機性好,在計算機上容易實作,占用記憶體較少(mt19937的C程式碼執行僅需624個字的工作區域),與其它已使用的僞随機數發生器相比,産生随機數的速度快、周期長,可達到2^19937-1,且具有623維均勻分布的性質,對于一般的應用來說,足夠大了,序列關聯比較小,能通過很多随機性測試。

voidtest_mt19937()

{

    // 以時間為種子建立一個随機數發生器

    boost::mt19937 rng(time(0));

    for (int i = 0; i < 100; ++i)

    {

       std::cout << rng() <<std::endl;

    }

}

不做過多解釋,boost的強大有點反人類了!