一.僞隨機數
剛接觸隨機數的時候,覺得計算機好神奇啊,可以得到我想要的任意數字,但是接觸多了就發現計算機產生的隨機數相似度如此之高,爲什麼運行多次產生的都是這幾個數呢,也不是特別隨機嘛。後來通過查閱資料才發現,計算機產生隨機數字的方法其實產生的是僞隨機數字。如果不採取一些措施,計算機總是應用同樣的過程來產生一系列隨機數字。這意味着每次運行程序都產生一樣的結果。但是計算機爲什麼不產生真正的隨機數呢,是做不到嗎?答案當然是否定的了。
二.爲什麼要使用僞隨機數呢?
大家可能覺得既然計算機可以產生真正的隨機數,那麼爲什麼不用呢。我們舉一個小例子來解釋一下:例如我們經常玩的貪吃蛇遊戲中果實的位置是隨機產生的,如果在某一時刻,果實出現在了某處,但是程序出錯了,你想跟蹤調試,但是你發現已經找不到原來那個出錯的地方了,因爲果實的產生是完全隨機的。這樣你的這個遊戲就存在了潛在的錯誤,你自己知道,但是卻不能發現錯誤源在哪裏。所以爲了避免這種錯誤,調試時還是使用僞隨機數比較好,直到覺得程序確實沒有錯誤了,可以考慮使用完全隨機數。
三.隨機數
完全隨機函數通過初始化內部的僞隨機數字生成器來使得每一次運行程序產生不同的結果,這正是編寫遊戲程序時所需要的。如果您要寫一個用到隨機數字的程序,那麼在調試階段最好不要調用完全隨機函數。當程序運行良好時,可以在主程序的開始調用完全隨機函數,從而使得程序運行發生變化。
四.有關隨機數字的函數
int rand(void);
和其他大多數函數不同的是,rand每次調用時返回的結果不同。rand的結果一定是非負的,且不大於常量RAND_MAX,常量RAND_MAX是在stdlib.h中定義的。因此,每次調用rand時,它都返回一個0到RAND_MAX之間的整數,包括邊界。
RAND_MAX的值取決於計算機系統。當編寫用到隨機數字的程序時,您不能主觀臆斷RAND_MAX的值。可以通過代碼查看你的計算機系統中的RAND_MAX的值。
rand函數需要一個初始值,用於啓動整個過程的值,被稱爲隨機數字發生器的種子(seed)。Rand函數的實現是這樣的,每次運行程序時都把種子設爲一個常數值,這就是爲什麼該函數總是生成序列一樣的數值。來看一下下面的代碼:
例一:
#include<stdlib.h>
#include<stdio.h>
#define NTrials 10
int main()
{
int i;
printf("On this Computer,RAND_MAX is %d.\n",RAND_MAX);
printf("The first %d calls to rand return:\n",NTrials);
for(i=0;i<NTrials;i++)
{
printf("%10d\n",rand());
}
return 0;
}
運行結果爲:
On this Computer,RAND_MAX is 32767.
The first 10 calls to rand return:
41
18467
6334
26500
19169
15724
11478
29358
26962
24464
#include<stdio.h>
#define NTrials 10
int main()
{
int i;
printf("On this Computer,RAND_MAX is %d.\n",RAND_MAX);
printf("The first %d calls to rand return:\n",NTrials);
for(i=0;i<NTrials;i++)
{
printf("%10d\n",rand());
}
return 0;
}
運行結果爲:
On this Computer,RAND_MAX is 32767.
The first 10 calls to rand return:
41
18467
6334
26500
19169
15724
11478
29358
26962
24464
連續運行了好多遍,每次運行結果都運行結果還跟上面的一樣,這就說明了rand函數是一個僞隨機函數。
爲了改變這種序列,需要設置seed爲一個不同的值,這可以通過調用函數srand(seed)來實現。函數srand可以重置seed的值,從而每次得到的隨機數字的序列不一樣。通常的做法是使用系統的時鐘值作爲初始值。因爲時間是不斷變化的,所以隨機序列也是變化的。可以通過調用函數time檢索當前系統的時鐘值,該函數在ctime接口中有定義,之後把該結果轉化爲一個整數。這種技術可以使您寫出下面的語句,它對初始化僞隨機數字發生器的結果有不可預知的影響:
srand((int)time(NULL));
用一個例子看一下srand是不是真的能改變seed。
例二:
int main()
{
int i;
srand((int)time(NULL)); //改變seed的值
printf("The first %d calls to rand return:\n",NTrials);
for(i=0;i<NTrials;i++)
{
printf("%10d\n",rand());
}
return 0;
}
運行結果:
The first 10 calls to rand return:
25865
28610
24648
4690
4180
15648
22709
7359
22068
31563
再運行一次看看結果如何。
int main()
{
int i;
srand((int)time(NULL)); //改變seed的值
printf("The Second %d calls to rand return:\n",NTrials);
for(i=0;i<NTrials;i++)
{
printf("%10d\n",rand());
}
return 0;
}
運行結果:
The Second 10 calls to rand return:
26551
24784
7804
11714
7553
21061
20964
15223
16094
18421
{
int i;
srand((int)time(NULL)); //改變seed的值
printf("The Second %d calls to rand return:\n",NTrials);
for(i=0;i<NTrials;i++)
{
printf("%10d\n",rand());
}
return 0;
}
運行結果:
The Second 10 calls to rand return:
26551
24784
7804
11714
7553
21061
20964
15223
16094
18421
看來srand確實有效果哦。
五.如何將rand所得的結果轉化爲確定區間的數呢?
我們可以使用自定義函數RandomInteger來解釋這個轉化過程,它可以將0到RAND_MAX之間的隨機數字轉換爲參數low到high之間的數。這種實現有4個步驟:
正規化:該過程中的第一步是將rand的整數結果轉換爲一個在半開區間[0,1)之間的浮點型數d。爲達到此目的,我們所做的工作就是將rand的結果轉換爲double類型,然後除以該範圍中的元素個數。因爲RAND_MAX是機器可以提供的最大整數,在加1之前將它轉爲double類型是很重要的,這樣可以確保除法得到的結果嚴格小於1.
縮放:第二步是值d乘以要求範圍的大小,這樣它就能包含要求的整數個數。因爲要求的範圍包括端點,low和high,所以得到的範圍中的整數個數爲high-low+1.
舍尾:第三步是通過舍掉小數點後面的尾數,使用類型強制轉換將數字換回成整數。這樣就得到一個下限爲0的一個隨機整數。
平移:最後一步是加上下限low,以符合所要求的下限。
RandomInteger函數的實現如下代碼:
int RandomInteger(int low,int high)
{
int k;
double d;
d=(double) rand()/((double) RAND_MAX+1);
k=(int)(d*(high-low+1));
return (low+k);
}
{
int k;
double d;
d=(double) rand()/((double) RAND_MAX+1);
k=(int)(d*(high-low+1));
return (low+k);
}
我們用一個小小的例子來測試一下這個函數能否達到目的。
例三:
#include<stdlib.h>
#include<stdio.h>
#include<ctime>
#define NTrials 10
int RandomInteger(int low,int high)
{
int k;
double d;
d=(double) rand()/((double) RAND_MAX+1);
k=(int)(d*(high-low+1));
return (low+k);
}
int main()
{
int i;
srand((int)time(NULL));
printf("%d calls to RandomInteger return:\n",NTrials);
for(i=0;i<NTrials;i++)
{
printf("%4d",RandomInteger(0,100));
}
printf("\n");
return 0;
}
運行結果:
10 calls to RandomInteger return:
91 85 98 90 8 37 40 92 8 19
#include<stdio.h>
#include<ctime>
#define NTrials 10
int RandomInteger(int low,int high)
{
int k;
double d;
d=(double) rand()/((double) RAND_MAX+1);
k=(int)(d*(high-low+1));
return (low+k);
}
int main()
{
int i;
srand((int)time(NULL));
printf("%d calls to RandomInteger return:\n",NTrials);
for(i=0;i<NTrials;i++)
{
printf("%4d",RandomInteger(0,100));
}
printf("\n");
return 0;
}
運行結果:
10 calls to RandomInteger return:
91 85 98 90 8 37 40 92 8 19
浮點數的實現過程與整數的實現過程類似,只是不需要第三步,函數實現如下:
double RandomReal(double low,double high)
{
double d;
d=(double) rand()/ ((double)RAND_MAX+1);
return (low+d*(high-low));
}
{
double d;
d=(double) rand()/ ((double)RAND_MAX+1);
return (low+d*(high-low));
}
通過一下例子看一下它的運行結果如何:
例四:
#include<stdlib.h>
#include<stdio.h>
#include<ctime> //srand所在的頭文件
#define NTrials 10
double RandomReal(double low,double high)
{
double d;
d=(double) rand()/ ((double)RAND_MAX+1);
return (low+d*(high-low));
}
int main()
{
int i;
srand((int)time(NULL));
printf("%d calls to RandomReal return:\n",NTrials);
for(i=0;i<NTrials;i++)
{
printf("%6.2f",RandomReal(2.5,12.8));
}
printf("\n");
return 0;
}
運行結果:
10 calls to RandomReal return:
12.68 4.89 9.09 8.53 12.74 5.24 10.30 7.33 12.44 7.93
#include<stdio.h>
#include<ctime> //srand所在的頭文件
#define NTrials 10
double RandomReal(double low,double high)
{
double d;
d=(double) rand()/ ((double)RAND_MAX+1);
return (low+d*(high-low));
}
int main()
{
int i;
srand((int)time(NULL));
printf("%d calls to RandomReal return:\n",NTrials);
for(i=0;i<NTrials;i++)
{
printf("%6.2f",RandomReal(2.5,12.8));
}
printf("\n");
return 0;
}
運行結果:
10 calls to RandomReal return:
12.68 4.89 9.09 8.53 12.74 5.24 10.30 7.33 12.44 7.93