Rand函数使用和对补码的理解
下面是在牛客網看到的一道題;
//假設這n個數的序號依次為0,1,2,...,n-1,數組名為num
void knuth1(int* pNum, int m, int n){srand((unsigned int)time(0));for (int i=0; i<n; i++){if (rand()%(n-i) < m)//rand()%(n-i)的取值范圍是[0, n-i){cout << pNum[i] << endl;m--;}}}這是牛客網上的一道題,目的是從n個數中可放回地隨機抽取m個數字。注意數字是可放回的,所以n個數字每一個數字被cout的概率都是m/n。當i取0,rand()%(n-i)的取值在是[0,n-1]范圍隨機分布,小于m的概率自然是m/n。當i取1,隨機數的范圍在[0,n-2],共n-1個取值。這時m的值要取決于i=0時有沒有輸出,所以可以用全概率公式計算。
這里想說的不是這道題本身,而是這個rand()函數。Rand()函數括號內是沒有參數的,直接返回[0,RAND_MAX]的隨機整數。但需要注意的是rand產生的是偽隨機數,用線性同余法實現,依然是一個有限狀態轉換機,依然有周期(周期很長),所以當我們再一次調用這個函數,得到的結果相同,這在我們調試的時候很方便,但如果需要產生真正的隨機數就需要srand來設置隨機種子了。void srand (unsigned int seed);直接調用rand時,種子的值默認是1.要得到真正的隨機數,每次設置的種子也應該不一樣,我們通常使用time(0)作為種子,即把系統時間作為種子,保證了不同時刻得到的種子是不一樣的。
在matlab中,rand函數就可以直接得到真正的隨機數。為了在不同時刻運行函數時得到相同的隨機數,便于調試,我們需要把隨機數生成器初始化:RAND('state',0)。但是這一用法在新的matlab版本中不再支持,而推薦使用RNG。
在編譯器中可看到RAND_MAX是一個宏定義,為0x7fff,也就是二進制的15位1. 百度百科中有:(C11)標準中未規定 RAND_MAX 的具體數值。但該標準規定了RAND_MAX 的值應至少為32767,最大為2147483647.這就引出了一個問題:int型明明在32位系統和64位系統中都占4字節,為什么這里產生的隨機數的最大值只是15位全1的二進制和31位全1的二進制?其實,這就是帶符號的short int型和int型的正數的最大值。于是,就有了第二個,也是很基本的一個問題,int型表示的范圍是什么,正整數和負整數都是怎么表示的?(慚愧)
我們以一個字節長度為例。先不用管書上所強行灌輸的數目符號位,原碼,補碼,我們從頭開始,自己試著解決問題。8bit編碼方式有2的8次方共256種,在圖像中可以表示[0,255]的灰度級,在圖像中像素取值只能是0或者正數,在計算機中,我們當然還需要表示負數,那么負數(先研究負整數)是怎么表示的呢?一個最自然而然的方式是把256種編碼方式的一部分表示正數,一部分表示負數,一部分表示0.我們把0000 0001~01111 1111這一部分用來表示正整數,因為這一部分從0開始,是最符合我們數數的習慣的。那么現在的問題就是如何把剩下的表示負數。
首先,我們可以觀察到剩下的部分除了0000 0000,最高位都是1,這就可以解釋,為什么最高位的1來表示負數。那么1000 0000~1111 1111到底和負數是怎么對應的呢?一個理所當然的思路是1111 1111=-1*(0111 1111)=-127。我們來驗證一下,1111 1111+0111 1111=0?明顯不等于,但同時也給了我們一個思路,可以利用已有的正整數表達方法和絕對值相等的正負數之和為0的特點求負整數的表達方式。-1的二進制形式等于
0000 0000-0000 0001=1111 1111+0000 0001-0000 0001=1111 1111
于是我們知道,1111 1111對應的是-1.上面的式子還告訴了我們更多:0000 0000可以寫做全1的數再加1,進位舍去就是全0.并且我們發現,將0拆分成全1和1的和,這樣我們求-A的補碼=全1-A+1,全1和二進制的加減都相對于異或,也就是取反,所以我們也終于得到了所謂的求負數補碼的方法:按位取反再加1.
于是我們可以得到-2的補碼:1111 1111+0000 0001-0000 0010=1111 1110
現在再考慮幾個特殊的數,128=1000 0000,-128的補碼=1000 0000,可見自然數128=-128的補碼形式,由于我們已經規定了最高位是符號位,符號位1表示負數,所以0~255是代表補碼時只有-128,沒有128.于是我們也得到了所謂的一字節帶符號整數取值范圍[-128,127].
這樣,我們得到規律,原來的0~255的數被分成兩部分,[0,127]是遞增的正數,和原來的表示方法一樣,之后的數代表負數,也是遞增。
到這里我們依然沒有解釋一句話,補碼是為了讓計算機把減法當做加法來做。其實,我們數軸首尾相接形成一個圓就好理解了。剛才我們也提到了,計算機中的加法超過長度會高位舍去,這其實意味著計算機中的數字是閉環的狀態機。無論是加還是減,都是在這個閉環里面移位,只不過是逆時針還是順時針罷了。我們把時鐘的十二點位置看作是0/255,加法看作是順時針移位(藍色曲線),減法是逆時針(黃色曲線),這樣六點鐘附近是加數和減數絕對值最大的位置。為了避免減法(逆時針),我們可以順時針移動相比于逆時針較大的角度達到相同的效果。逆時針轉動30度就相當于順時針轉動330度,而330度就可以用時鐘上的刻度來衡量,即0~255就是時鐘的刻度。330度就是30度的補角,這也是補碼的來歷。
在查閱關于rand的使用的過程中,看到了一個例子,產生[0,10]之間的隨機數:
#include<stdlib.h>int main(){int i,j;for(i=0; i<10; i++){j=1+(int)(10.0 * rand()/(RAND_MAX+1.0));printf("%d ",j);}}?
產生介于 1 到 10 間的隨機數值。這里的問題是為什么要加1.0?我的理解是如果分母取RAND_MAX,那么隨機數范圍就被歸一化到[0,1],乘10后范圍是[0,10],而我們的目標是先取得[0,9]的隨機數再加1才能滿足要求。注意到這里的加法和乘法都是float型,最后被強制轉換成int型,其實這才是關鍵。分母取(最大值+1),使得隨機數歸一化后無法取到1,乘10之后范圍是[0,10),最大值在9和10之間。而int強制轉換是直接取浮點數的整數部分,這樣我們就得到了范圍在[0,9]的隨機數。
P.s 浮點數到整型的轉換,除了直接取整數部分,還有ceil函數和floor函數。Floor函數是取小于等于浮點數的整數,這一點與直接取整數部分在浮點數是負數時結果有區別。
最后,關于歸一化方法和使用線性同余法得到想要的范圍內的隨機數的區別,有人說是前者是在多次隨機出來的結果,前者理論上會更平均,而后者僅僅是和10求余得到的結果,沒前面的結果來得平均。關于這個說法還不是很懂,有空可以再研究一下線性同余法。
Reference:
總結
以上是生活随笔為你收集整理的Rand函数使用和对补码的理解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 数据结构之DFS与BFS实现
- 下一篇: 神经网络相关的笔试题目集合(一)