热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

c++隨機亂數srand()和rand()

所謂的「偽隨機數」指的並不是假的隨機數,這裡的「偽」是有規律的意思。其實絕對的隨機數隻是一種理想狀態的隨機數,計算機只能生成相對的隨機數即偽隨機數。計算機生成的偽隨機數既是隨機的又是有規律的——一
所謂的「偽隨機數」指的並不是假的隨機數,這裡的「偽」是有規律的意思。其實絕對的隨機數隻是一種理想狀態的隨機數,計算機只能生成相對的隨機數即偽隨機數。計算機生成的偽隨機數既是隨機的又是有規律的 —— 一部份遵守一定的規律,一部份則不遵守任何規律。比如「世上沒有兩片形狀完全相同的樹葉」,這正點到了事物的特性 —— 規律性;但是每種樹的葉子都有近似的形狀,這正是事物的共性 —— 規律性。從這個角度講,我們就可以接受這樣的事實了:計算機只能產生偽隨機數而不是絕對的隨機數。

C++中的標準庫(包含在中)提供兩個幫助生成偽隨機數的函數:rand()和srand()。
函數一:int rand(void);
從srand(seed)中指定seed開始,返回一個範圍介於[seed,RAND_MAX(0x7fff))的隨機整數
函數二:void srand(unsigned seed);
參數seed是rand()的隨機種子,即用來初始化rand()的起始值。

系統在調用rand()之前都會自動調用srand(),如果用戶在rand()之前曾調用過srand()給參數seed指定了一個值,那麼rand()就會將seed的值作為產生偽隨機數的初始值;而如果用戶在rand()前沒有調用過srand(),那麼rand()就會自動調用srand(1),即系統默認將1作為偽隨機數的初始值。

由上述可得知,如果希望rand()在每次程序運行時產生的值都不一樣,必須給srand(seed)中的參數seed指定一個變值,這個變值必須在每次程序運行時都不一樣(比如到目前為止流逝的時間);如果我們給seed指定的是一個定值,那麼每次程序運行的時候,rand()產生的隨機數都會一樣,只不過這個值是[seed,RAND_MAX(0x7fff))範圍中的一個隨機取得的值。

舉幾個例子說明一下,假設我們要取得0~6之間的隨機數(不包括6本身):
程序一(沒有指定seed的值):
for(int i=0;i<10;i++)
{
ran_num=rand()%6;
cout<}
每次運行程序一都將輸出:5 5 4 4 5 4 0 0 4 2

程序二(指定seed為1):
srand(1);
for(int i=0;i<10;i++)
{
ran_num=rand()%6;
cout<}
每次運行程序二都將輸出:5 5 4 4 5 4 0 0 4 2,跟程序一的結果完全一樣。

程序三(指定seed的值為6):
srand(6);
for(int i=0;i<10;i++)
{
ran_num=rand()%6;
cout<}
每次運行程序三都將輸出:4 1 5 1 4 3 4 4 2 2,雖然值跟程序二不一樣,不過每次運行時的結果也都相同。

程序四(指定seed的值為當前系統流逝了的時間,單位為秒(time_t time(0))):
#include
……
srand((unsigned)time(0));
for(int i=0;i<10;i++)
{
ran_num=rand()%6;
cout<}
運行程序四的時候,第一次輸出:0 1 5 4 5 0 2 3 4 2,第二次輸出:3 2 3 0 3 5 5 2 2 3,... ...每次的運行結果都不一樣,因為每次啟動程序時的時刻都不同。

關於time_t time(0)
time_t 被定義為長整型,它將返回從1970年1月1日零時零分零秒到現在所經歷過的時間,單位為秒。比如輸出 cout<
關於ran_num=rand()%6
將rand()的返回值與6求模是必須的,這樣才能確保目的隨機數落在[0,6)之間,否則很可能會得到一個非常巨大的數值(RAND_MAX一般為32767)。一個通用的公式是:要想取得[a,b)之間的隨機整數,使用(rand()%(b-a))+ a,結果包含 a 而不含 b 。

百度百科
首先需要聲明的是,計算機不會產生絕對隨機的隨機數,計算機只能產生「偽隨機數」。其實絕對隨機的隨機數隻是一種理想的隨機數,即使計算機怎樣發展,它也不會產生一串絕對隨機的隨機數。計算機只能生成相對的隨機數,即偽隨機數。
偽隨機數並不是假隨機數,這裡的「偽」是有規律的意思,就是計算機產生的偽隨機數既是隨機的又是有規律的。怎樣理解呢?產生的偽隨機數有時遵守一定的規律,有時不遵守任何規律;偽隨機數有一部分遵守一定的規律;另一部分不遵守任何規律。比如「世上沒有兩片形狀完全相同的樹葉」,這正是點到了事物的特性,即隨機性,但是每種樹的葉子都有近似的形狀,這正是事物的共性,即規律性。從這個角度講,你大概就會接受這樣的事實了:計算機只能產生偽隨機數而不能產生絕對隨機的隨機數。(嚴格地說,這裡的計算機是指由馮諾依曼思想發展起來的電子計算機。而未來的量子計算機有可能產生基於自然規律的不可重現的「真」隨機數)
那麼計算機中隨機數是怎樣產生的呢?有人可能會說,隨機數是由「隨機種子」產生的。沒錯,隨機種子是用來產生隨機數的一個數,在計算機中,這樣的一個「隨機種子」是一個無符號整形數。那麼隨機種子是從哪裡獲得的呢?
下面看這樣一個C程序:
//rand01.c
#include
static unsigned int RAND_SEED;
unsigned int random(void)
{
RAND_SEED=(RAND_SEED*123+59)%65536;
return(RAND_SEED);
}
void random_start(void)
{
int temp[2];
movedata(0x0040,0x006c,FP_SEG(temp),FP_OFF(temp),4);
RAND_SEED=temp[0];
}
main()
{
unsigned int i,n;
random_start();
for(i=0;i<10;i++)
printf("%u\t",random());
printf("\n");
}
這個程序(rand01.c)完整地闡述了隨機數產生的過程:
首先,主程序調用random_start()方法,random_start()方法中的這一句我很感興趣:
movedata(0x0040,0x006c,FP_SEG(temp),FP_OFF(temp),4);
這個函數用來移動內存數據,其中FP_SEG(far pointer to segment)是取temp數組段地址的函數,FP_OFF(far pointer to offset)是取temp數組相對地址的函數,movedata函數的作用是把位於0040:006CH存儲單元中的雙字放到數組temp的聲明的兩個存儲單元中。這樣可以通過temp數組把0040:006CH處的一個16位的數送給RAND_SEED。
random用來根據隨機種子RAND_SEED的值計算得出隨機數,其中這一句:
RAND_SEED=(RAND_SEED*123+59)%65536;
是用來計算隨機數的方法,隨機數的計算方法在不同的計算機中是不同的,即使在相同的計算機中安裝的不同的操作系統中也是不同的。我在linux和windows下分別試過,相同的隨機種子在這兩種操作系統中生成的隨機數是不同的,這說明它們的計算方法不同。
現在,我們明白隨機種子是從哪兒獲得的,而且知道隨機數是怎樣通過隨機種子計算出來的了。那麼,隨機種子為什麼要在內存的0040:006CH處取?0040:006CH處存放的是什麼?
學過《計算機組成原理與接口技術》這門課的人可能會記得在編制ROM BIOS時鐘中斷服務程序時會用到Intel 8253定時/計數器,它與Intel 8259中斷芯片的通信使得中斷服務程序得以運轉,主板每秒產生的18.2次中斷正是處理器根據定時/記數器值控制中斷芯片產生的。在我們計算機的主機板上都會有這樣一個定時/記數器用來計算當前系統時間,每過一個時鐘信號週期都會使記數器加一,而這個記數器的值存放在哪兒呢?沒錯,就在內存的 0040:006CH處,其實這一段內存空間是這樣定義的:
TIMER_LOW DW ? ;地址為 0040:006CH
TIMER_HIGH DW ? ;地址為 0040:006EH
TIMER_OFT DB ? ;地址為 0040:0070H
時鐘中斷服務程序中,每當TIMER_LOW轉滿時,此時,記數器也會轉滿,記數器的值歸零,即TIMER_LOW處的16位二進制歸零,而TIMER_HIGH加一。rand01.c中的
movedata(0x0040,0x006c,FP_SEG(temp),FP_OFF(temp),4);
正是把TIMER_LOW和TIMER_HIGH兩個16位二進制數放進temp數組,再送往RAND_SEED,從而獲得了「隨機種子」。
現在,可以確定的一點是,隨機種子來自系統時鐘,確切地說,是來自計算機主板上的定時/計數器在內存中的記數值。這樣,我們總結一下前面的分析,並討論一下這些結論在程序中的應用:
1.隨機數是由隨機種子根據一定的計算方法計算出來的數值。所以,只要計算方法一定,隨機種子一定,那麼產生的隨機數就不會變。
看下面這個C++程序:
//rand02.cpp
#include
#include
using namespace std;
int main()
{
unsigned int seed=5;
srand(seed);
unsigned int r=rand();
cout}
在相同的平台環境下,編譯生成exe後,每次運行它,顯示的隨機數都是一樣的。這是因為在相同的編譯平台環境下,由隨機種子生成隨機數的計算方法都是一樣的,再加上隨機種子一樣,所以產生的隨機數就是一樣的。
2.只要用戶或第三方不設置隨機種子,那麼在默認情況下隨機種子來自系統時鐘(即定時/計數器的值)
看下面這個C++程序:
//rand03.cpp
#include
#include
using namespace std;
int main()
{
srand((unsigned)time(NULL));
unsigned int r=rand();
cout}
這裡用戶和其他程序沒有設定隨機種子,則使用系統定時/計數器的值做為隨機種子,所以,在相同的平台環境下,編譯生成exe後,每次運行它,顯示的隨機數會是偽隨機數,即每次運行顯示的結果會有不同。
3.建議:如果想在一個程序中生成隨機數序列,需要至多在生成隨機數之前設置一次隨機種子。
看下面這個用來生成一個隨機字符串的C++程序:
//rand04.cpp
#include
#include
using namespace std;
int main()
{
int rNum,m=20;
char *ch=new char[m];
for ( int i = 0; i //大家看到了,隨機種子會隨著for循環在程序中設置多次
srand((unsigned)time(NULL));
rNum=1+(int)((rand()/(double)RAND_MAX)*36); //求隨機值
switch (rNum){
case 1: ch='a';
break ;
case 2: ch='b';
break ;
case 3: ch='c';
break ;
case 4: ch='d';
break ;
case 5: ch='e';
break ;
case 6: ch='f';
break ;
case 7: ch='g';
break ;
case 8: ch='h';
break ;
case 9: ch='i';
break ;
case 10: ch='j';
break ;
case 11: ch='k';
break ;
case 12: ch='l';
break ;
case 13: ch='m';
break ;
case 14: ch='n';
break ;
case 15: ch='o';
break ;
case 16: ch='p';
break ;
case 17: ch='q';
break ;
case 18: ch='r';
break ;
case 19: ch='s';
break ;
case 20: ch='t';
break ;
case 21: ch='u';
break ;
case 22: ch='v';
break ;
case 23: ch='w';
break ;
case 24: ch='x';
break ;
case 25: ch='y';
break ;
case 26: ch='z';
break ;
case 27:ch='0';
break;
case 28:ch='1';
break;
case 29:ch='2';
break;
case 30:ch='3';
break;
case 31:ch='4';
break;
case 32:ch='5';
break;
case 33:ch='6';
break;
case 34:ch='7';
break;
case 35:ch='8';
break;
case 36:ch='9';
break;
}//end of switch
cout<}//end of for loop
cout}
而運行結果顯示的隨機字符串的每一個字符都是一樣的,也就是說生成的字符序列不隨機,所以我們需要把 srand((unsigned)time(NULL)); 從for循環中移出放在for語句前面,這樣可以生成隨機的字符序列,而且每次運行生成的字符序列會不同(呵呵,也有可能相同,不過出現這種情況的幾率太小了)。
如果你把srand((unsigned)time(NULL));改成srand(2);這樣雖然在一次運行中產生的字符序列是隨機的,但是每次運行時產生的隨機字符序列串是相同的。把srand這一句從程序中去掉也是這樣。
此外,你可能會遇到這種情況,在使用timer控件編製程序的時候會發現用相同的時間間隔生成的一組隨機數會顯得有規律,而由用戶按鍵command事件產生的一組隨機數卻顯得比較隨機,為什麼?根據我們上面的分析,你可以很快想出答案。這是因為timer是由計算機時鐘記數器精確控制時間間隔的控件,時間間隔相同,記數器前後的值之差相同,這樣時鐘取值就是呈線性規律的,所以隨機種子是呈線性規律的,生成的隨機數也是有規律的。而用戶按鍵事件產生隨機數確實更呈現隨機性,因為事件是由人按鍵引起的,而人不能保證嚴格的按鍵時間間隔,即使嚴格地去做,也不可能完全精確做到,只要時間間隔相差一微秒,記數器前後的值之差就不相同了,隨機種子的變化就失去了線性規律,那麼生成的隨機數就更沒有規律了,所以這樣生成的一組隨機數更隨機。這讓我想到了各種晚會的抽獎程序,如果用人來按鍵產生幸運觀眾的話,那就會很好的實現隨機性原則,結果就會更公正。
最後,我總結兩個要點:
1.計算機的偽隨機數是由隨機種子根據一定的計算方法計算出來的數值。所以,只要計算方法一定,隨機種子一定,那麼產生的隨機數就是固定的。
2.只要用戶或第三方不設置隨機種子,那麼在默認情況下隨機種子來自系統時鐘。

推荐阅读
author-avatar
宋紫紫云__
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有