C++批量生成大規模隨機數中種子值設定的總結——解決隨機數序列重複問題
今天編了一個遺傳演算法的小程式。在個體類的預設建構函式中,使用隨機數來進行二進位制編碼的初始化。C++中的隨機數其實是偽隨機數,即隨機數函式產生的是通過公式計算出來的一系列偽隨機數,這個公式會採用一個種子數計算出一個數,而該數將成為產生下一個數的種子數。基於產生隨機數的原理,如果採用相同的種子的話,兩次呼叫隨機數後產生的隨機數序列將是一樣的。一般情況下,在C++中使用當前時間作為種子數,這樣兩次呼叫隨機函式產生的隨機數序列就會不同,進而達到更加隨機的效果。對於C/C++而言,通常產生的隨機數的方法是呼叫以下兩個函式:
srand(time(NULL)); // 設定隨機數種子, 引數內是使用當前時間作為種子
rand(); // 產生一個隨機數
在種群類中,建立了一個個體類的陣列。建立一個種群類的物件,發現使用種群類的預設建構函式得到的個體,都有著完全相同的二進位制編碼。原因是time(NULL)所取得的時間只能精確到秒,而建立一個物件陣列所花費的時間顯然遠遠低於這個數量級。
可以採用一種比較費力的解決辦法。那就是在個體類中增加一個函式,用來設定個體類的二進位制串。使用一個unsigned型別的形式引數,這個引數的作用是作為該函式內部的隨機數種子。另外,還要在種群類的預設建構函式中增加程式碼,產生一個隨機數序列,並將這些隨機數作為上述個體類新增函式的引數,用來修改個體類中二進位制串的內容,實現個體物件陣列中二進位制串的隨機化。但是這樣要增加很多程式碼,很麻煩,也有為面向物件程式設計的原則,增加了群體類的工作量。
通過在網上查詢資料,有文章說使用結構體timeb與函式ftime可以取得毫秒級的時間,設定隨機數種子的程式碼如下:
struct timeb stb;
ftime(&stb);
srand((unsigned)stb.millitm)
經過測試,發現這一所謂毫秒級的時間種子數仍然不能解決問題。
繼續查資料,發現一種可以獲得CPU高精度時間戳的方法,程式碼如下:
__declspec (naked) unsigned __int64 GetCpuCycle( void )
{
_asm
{
rdtsc
ret
}
}
其中,RDTSC指令的返回值存放在EDX EAX中, EDX為高32位,EAX為低32位。 RDTSC 指令即(Read Time Stamp Counter , 獲得CPU的高精度時間戳。這樣一來我們就可以獲得當前的CPU自上電以來的時間週期數了:
unsigned __int64 iCpuCycle = GetCpuCycle();
因此,我們可以使用iCpuCycle 的低32位作為隨機數的種子,程式碼如下:
unsigned srnd=(unsigned)iCpuCycle;
srand(srnd);
經過測試,上述方法可行。但是這種方法的缺點是依賴於處理器,有些計算機的處理器可能不支援該指令。
繼續查資料,又找到一種可行的方法,該方法具有微秒級的精度,即使用QueryPerformanceCounter函式,它可以返回高精度的計數器值,其精度可達微秒級。通常該函式會與函式QueryPerformanceFrequency配合使用,函式QueryPerformanceFrequency的功能是如果當前機器存在定時器則查詢出當前機器定時器的頻率,我們可以利用QueryPerformanceFrequency測試當前系統裡是否有這個高精度的定時器,如果有,則可以呼叫QueryPerformanceCounter獲得一個精度很高的計數值。程式碼如下:
#include<windows.h>
LARGE_INTEGER nFrequency;
if(::QueryPerformanceFrequency(&nFrequency))
{
LARGE_INTEGER nStartCounter;
::QueryPerformanceCounter(&nStartCounter);
::srand((unsigned)nStartCounter.LowPart);
}
#include <Windows.h>
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
int count[100] = {0};
LARGE_INTEGER nFrequency;
for(int i=0;i<10000000;++i)
{
if(::QueryPerformanceFrequency(&nFrequency))
{
LARGE_INTEGER nStartCounter;
::QueryPerformanceCounter(&nStartCounter);
::srand((unsigned)nStartCounter.LowPart);
count[rand()%100] ++;
}
}
for(int i=0;i<100;++i)
{
cout<<i<<" : "<<count[i]<<endl;
}
return 0;
}
經測試,使用這種方法也可以實現我的問題中的物件陣列的真正的隨機初始化。