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