自己寫的一個C++高位長真/偽隨機數發生器類
一直想要弄一個能夠生成真隨機數的類,但因未能找到合適的真隨機因子而未能完成。前些天偶然瞭解到IA32的CPU具有一個時鐘週期計數器,能夠提供自CPU復位後至今累計的時鐘週期數。忽然覺得這個正是最易得到而又最難預測的真隨機因子。
這裡我們不討論嚴格意義上的“真隨機”數,以免陷入無盡的口水裡。我認為由於系統執行狀況的不確定性,實際上連總是以固定間隔獲取時鐘計數都很難做到。再加上多核處理器各核心的負載變化、睿頻功能的狀態變更、CPU流水線被中斷的情況不同、CPU快取的命中變化、作業系統的任務排程、真正隨機的外界事件干擾(比如鍵盤/滑鼠操作,網路通訊變化等等)......如此多的變數影響下,我們獲得的時鐘計數完全無法預測。用它做真隨機因子的隨機數演算法就已經是真正的隨機數了。至少,對於日常的需求而言已經可以得到滿足了。
和通常使用的系統時間毫秒數相比較,這個時鐘週期計數更為多變。系統時間一毫秒才改變一次,足夠一個快速的隨機數發生器產生超過十萬個隨機數了。因為期間系統時間沒有改變,用它生成的隨機數其實還是一個固定序列。而時鐘計數則能保證每次呼叫都不相同,生成一個隨機數的時間至少要經過幾十個時鐘週期了。使用它來生成隨機數能保證不會出現固定序列的現象。
另一個好處是執行效率很高。系統時間的獲取需要呼叫作業系統API,費時費力。而時鐘計數則僅需幾條彙編命令就能取到,輕巧容易。
網上曾有人直接把時鐘週期計數的低位作為隨機數來使用,但我認為這還是不妥。畢竟該數字重複一次的週期太長了點,在此期間使用者獲得的就是一個雖然間距隨機但卻一直在不斷增大的數字序列,怎麼看都不像是一個隨機數。所以,我把他作為隨機因子新增到線性同餘法隨機數發生器的演算法裡,使得到的數字序列成為真正無規律的真隨機序列。
這個演算法的執行效率還是非常高的,不帶引數時大約能在幾納秒的時間裡生成一個真隨機數。帶上引數後63位演算法的速度下降很多,大約需要15納秒才能生成一個。原因是它需要做128位乘法運算來整理返回值。我最初用浮點數來做返回值整理,速度更快,但發現計算誤差太可惡了:用n做引數本該生成0到n-1的數值,結果除錯中改置極限引數後竟然在輸出裡看到了n。於是改用內聯彙編來做整理運算。
// RandomDigit.h
// 定義了一個高速真/偽隨機數類,可用以快速生成隨機數
// 偽隨機數可通過設定種子來生成特定序列
// 真隨機數沒有固定序列,每次生成都隨機器狀態而改變,無法預測
class RandomDigit
{
private:
static int seed;
static long long seedH;
static unsigned int arrange(unsigned int n); // 整理函式,用於將隨機數整理成指定範圍的整數
static unsigned long long arrange(unsigned long long n); // 高精度過載型式
public:
static void SetSeed(long long seed); // 設定隨機數種子
static int Random(); // 獲取31位隨機數
static unsigned int Random(unsigned int n); // 獲取自0~n-1之間的一個隨機整數
static long long RandomHight(); // 獲取63位隨機數
static unsigned long long RandomHight(unsigned long long n); // 獲取自0~n-1之間的一個隨機整數
static int RealRandom(); // 獲取31位真隨機數
static unsigned int RealRandom(unsigned int n); // 獲取0~n-1之間的一個真隨機整數
static long long RealRandomHight(); // 獲取63位真隨機數
static unsigned long long RealRandomHight(unsigned long long n);// 獲取0~n-1之間的一個真隨機整數
};
// RandomDigit.cpp
#include "RandomDigit.h"
int RandomDigit::seed;
long long RandomDigit::seedH;
void RandomDigit::SetSeed(long long seed)
{
seed=seedH=seed;
}
int RandomDigit::Random()
{
return seed=((seed<<16)+seed+0x1db3d743)&0x7fffffff;
}
unsigned int RandomDigit::Random(unsigned int n)
{
Random();
return arrange(n);
}
long long RandomDigit::RandomHight()
{
return seedH=((seedH<<32)+seedH+0xdb3d742c265539d)&0x7fffffffffffffff;
}
unsigned long long RandomDigit::RandomHight(unsigned long long n)
{
RandomDigit::RandomHight();
return arrange(n);
}
int RandomDigit::RealRandom()
{
__asm
{
rdtsc
add eax,RandomDigit::seed
and eax,0x7fffffff
mov RandomDigit::seed,eax
}
return Random();
}
unsigned int RandomDigit::RealRandom(unsigned int n)
{
RandomDigit::RealRandom();
return arrange(n);
}
long long RandomDigit::RealRandomHight()
{
__asm
{
rdtsc
add dword ptr [RandomDigit::seedH],eax
adc eax,dword ptr [RandomDigit::seedH+4]
and eax,0x7fffffff
mov dword ptr [RandomDigit::seedH+4],eax
}
return RandomHight();
}
unsigned long long RandomDigit::RealRandomHight(unsigned long long n)
{
RandomDigit::RealRandomHight();
return arrange(n);
}
unsigned int RandomDigit::arrange(unsigned int n)
{
return (unsigned int)(((unsigned long long)seed*(unsigned long long)n)>>31);
}
unsigned long long RandomDigit::arrange(unsigned long long n)
{
// 浮點計算不安全,只好用匯編來加速了。
unsigned long long product;
__asm // 實現128位乘法( seedH * n ),並將結果右移63位後存入product中。
{
mov eax,dword ptr [seedH]
mul dword ptr [n]
mov ebx,edx
mov eax,dword ptr [seedH+4]
mul dword ptr [n]
add ebx,eax
xor ecx,ecx
adc ecx,edx
xor edx,edx
mov eax,dword ptr [n+4]
cmp eax,edx
jz zero
mul dword ptr [seedH]
add ebx,eax
adc ecx,edx
mov eax,dword ptr [n+4]
jc cseted
mul dword ptr [seedH+4]
jmp continuation
cseted:
mul dword ptr [n+4]
inc edx
continuation:
add ecx,eax
xor eax,eax
adc edx,eax
zero:
shl ebx,1
rcl ecx,1
rcl edx,1
mov dword ptr [product],ecx
mov dword ptr [product+4],edx
}
return product;
}