1. 程式人生 > >C語言,如何產生隨機數(各種轉載)

C語言,如何產生隨機數(各種轉載)

1. 基本函式     在C語言中取隨機數所需要的函式是:

intrand(void);
voidsrand(unsignedintn);

    rand()函式和srand()函式被宣告在標頭檔案stdlib.h中,所以要使用這兩個函式必須包含該標頭檔案:

#include<stdlib.h>

2. 使用方法

    rand()函式返回0到RAND_MAX之間的偽隨機數(pseudorandom)。RAND_MAX常量被定義在stdlib.h標頭檔案中。其值等於32767,或者更大。

    srand()函式使用自變數n作為種子,用來初始化隨機數產生器。只要把相同的種子傳入srand(),然後呼叫rand()時,就會產生相同的隨機數序列。因此,我們可以把時間作為srand()函式的種子,就可以避免重複的發生。

如果,呼叫rand()之前沒有先呼叫srand(),就和事先呼叫srand(1)所產生的結果一樣。

for(inti=0;i<10;i++)
{
printf("%d ",rand()%10);
}

    每次執行都將輸出:1 7 4 0 9 4 8 8 2 4

srand(1);
for(inti=0;i<10;i++)
{
    printf("%d ",rand()%10);
}

    每次執行都將輸出:1 7 4 0 9 4 8 8 2 4

例2的輸出結果與例1是完全一樣的。


srand(8);
for(inti=0;i<10;i++)
{
    printf("%d "

,rand()%10);
}

    每次執行都將輸出:4 0 1 3 5 3 7 7 1 5

   該程式取得的隨機值也是在[0,10)之間,與srand(1)所取得的值不同,但是每次執行程式的結果都相同。


srand((unsigned)time(NULL));
for(inti=0;i<10;i++)
{
    printf("%d ",rand()%10);
}

     該程式每次執行結果都不一樣,因為每次啟動程式的時間都不同。另外需要注意的是,使用time()函式前必須包含標頭檔案time.h。

3. 注意事項

  • 求一定範圍內的隨機數。

     如要取[0,10)之間的隨機整數,需將rand()的返回值與10求模。

randnumber=rand()%10;

     那麼,如果取的值不是從0開始呢?你只需要記住一個通用的公式。

    要取[a,b)之間的隨機整數(包括a,但不包括b),使用:

    (rand() % (b - a)) + a

  • 偽隨機浮點數。

     要取得0~1之間的浮點數,可以用:  

     rand() / (double)(RAND_MAX)

     如果想取更大範圍的隨機浮點數,比如0~100,可以採用如下方法:

    rand() /((double)(RAND_MAX)/100)

     其他情況,以此類推,這裡不作詳細說明。

     當然,本文取偽隨機浮點數的方法只是用來說明函式的使用辦法,你可以採用更好的方法來實現。

    舉個例子,假設我們要取得0~10之間的隨機整數(不含10本身):

大家可能很多次討論過隨機數在計算機中怎樣產生的問題,在這篇文章中,我會對這個問題進行更深入的探討,闡述我對這個問題的理解。

     首先需要宣告的是,計算機不會產生絕對隨機的隨機數,計算機只能產生“偽隨機數”。其實絕對隨機的隨機數只是一種理想的隨機數,即使計算機怎樣發展,它也不會產生一串絕對隨機的隨機數。計算機只能生成相對的隨機數,即偽隨機數。

     偽隨機數並不是假隨機數,這裡的“偽”是有規律的意思,就是計算機產生的偽隨機數既是隨機的又是有規律的。怎樣理解呢?產生的偽隨機數有時遵守一定的規律,有時不遵守任何規律;偽隨機數有一部分遵守一定的規律;另一部分不遵守任何規律。比如“世上沒有兩片形狀完全相同的樹葉”,這正是點到了事物的特性,即隨機性,但是每種樹的葉子都有近似的形狀,這正是事物的共性,即規律性。從這個角度講,你大概就會接受這樣的事實了:計算機只能產生偽隨機數而不能產生絕對隨機的隨機數。

     那麼計算機中隨機數是怎樣產生的呢?有人可能會說,隨機數是由“隨機種子”產生的。沒錯,隨機種子是用來產生隨機數的一個數,在計算機中,這樣的一個“隨機種子”是一個無符號整形數。那麼隨機種子是從哪裡獲得的呢?

     下面看這樣一個C程式:

//rand01.c

#include<dos.h>

staticunsignedintRAND_SEED;

unsignedintrandom(void)
{
    RAND_SEED=(RAND_SEED*123+59)%65536;
return(RAND_SEED);
}

voidrandom_start(void)
{
   inttemp[2];
    movedata(0x0040,0x006c,FP_SEG(temp),FP_OFF(temp),4);

RAND_SEED=temp[0];
}

main()
{
   unsignedinti,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)e536;

     是用來計算隨機數的方法,隨機數的計算方法在不同的計算機中是不同的,即使在相同的計算機中安裝的不同的作業系統中也是不同的。我在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<iostream>
#include<ctime>
usingnamespacestd;

intmain()
{
   unsignedintseed=5;
srand(seed);
   unsignedintr=rand();
   cout<<r<<endl;
}

      在相同的平臺環境下,編譯生成exe後,每次執行它,顯示的隨機數都是一樣的。這是因為在相同的編譯平臺環境下,由隨機種子生成隨機數的計算方法都是一樣的,再加上隨機種子一樣,所以產生的隨機數就是一樣的。

     2.只要使用者或第三方不設定隨機種子,那麼在預設情況下隨機種子來自系統時鐘(即定時/計數器的值)

     看下面這個C++程式:

//rand03.cpp

#include<iostream>
#include<ctime>
usingnamespacestd;

intmain()
{

srand((unsigned)time(NULL));
   unsignedintr=rand();
  cout<<r<<endl;
   return0;
}

     這裡使用者和其他程式沒有設定隨機種子,則使用系統定時/計數器的值做為隨機種子,所以,在相同的平臺環境下,編譯生成exe後,每次執行它,顯示的隨機數會是偽隨機數,即每次執行顯示的結果會有不同。

3.建議:如果想在一個程式中生成隨機數序列,需要至多在生成隨機數之前設定一次隨機種子。

    看下面這個用來生成一個隨機字串的C++程式:

//rand04.cpp

#include<iostream>
#include<time.h>
usingnamespacestd;

intmain()
{
intrNum,m=20;
   char*ch=newchar[m];

    for(inti=0;i<m;i++)

{
       
//大家看到了,隨機種子會隨著for迴圈在程式中設定多次
srand((unsigned)time(NULL));
        rNum=1+(int)((rand()/(double)RAND_MAX)*36);
//求隨機值

        switch(rNum)

{
       case1:ch[i]='a';
           break;
       case2:ch[i]='b';
           break;
       case3:ch[i]='c';
          break;
       case4:ch[i]='d';
           break;
       case5:ch[i]='e';
           break;
        case6:ch[i]='f';
           break;
       case7:ch[i]='g';
           break;
       case8:ch[i]='h';
           break;
       case9:ch[i]='i';
           break;
       case10:ch[i]='j';
          break;
       case11:ch[i]='k';
          break;
      case12:ch[i]='l';
           break;
        case13:ch[i]='m';
           break;