【深入理解計算機系統-第二版】第二章部分家庭作業(Homework)參考答案


這幾天一直在寫《深入理解計算機系統》第二版中第二章的家庭作業,費了幾天的時間,終於完成了。當初碰到若干題不會,在網上也沒有搜索到答案。現在,我把這份自己完成的答案分享上來,與大家交流思想。其中錯誤一定會存在,如果有錯誤,希望指出來,共同進步。

2.67

A:左移位數大於等於int長度。

B:可以考慮用兩次左移來實現<<32:

int set_msb=1<<31;

int beyond_msb=set_msb<<1;

C:可以考慮用三次左移來實現<<31與<<32:

int temp=1<<15;

temp<<=15;

int set_msb=temp<<1;

int beyond_msb=temp<<2;

 

2.68

Q:Clear all but least significant n bits of x,即保留x的低n位,1<=n<=w

A:可以參考2.66的提示。生成低n位全為1,其余位全為0的掩碼,通常想到的是

(1<<n)-1

但是n==w的時候就會出錯,左移的位數等於int長度(見2.67A),這時我們可以采用兩次左移來實現:先生成低n-1位全為1,其余位全為0的掩碼m,然后讓m左移1位,然后加1即可,手工檢驗n==1時此情形仍滿足,故最后的掩碼為{[1<<(n-1)-1]<<1+1},此掩碼與x相與即可:

int lower_bits(int x, int n)

{

       return ((1<<(n-1)-1)<<1+1)&x;

}

2.69

Q::Do rotating right shift. Assume 0<=n<w

A:實際上相當於將x循環右移n位。我們先取x的低n位,要特別注意n==0的情形,參照2.68,使用((1<<n)-1)&x來取得x的低n位,之所以不采用2.68最終答案所示的方法的原因是n可以取到0,但是取不到w,而2.68中,n取不到0,而能取到w。

然后我們將x的高(w-n)位右移n位,在題目假設(P154頁)中,右移操作是算數右移,高n位擴展了x的符號位,而我們需要的是邏輯右移。邏輯右移的解法如2.63中所示:

unsigned srl(unsigned x, int k)

{

       unsigned xsra = (int) x>>k;

       return ((1<<(w-k-1)-1)<<1+1)^xsra;

}

同時需要注意n為0的情形,處理該情形的方法如2.68所述。將x的低n位左移w-n位,異或上slr(x,n)即可:

((((1<<n)-1)&x)<<(w-n-1)<<1)^slr(x,n)

在上式中,同樣要注意k=0的情形。

 

2.70

Q:Return 1 when x can be represented as an n-bit, 2’s complement number; 0 otherwise. 1<=n<=w.

A:對於w位的二進制補碼數x,它能不能表示成n位的二進制補碼數需要看它的最高w-n+1位是不是全0(正數)或者全1(負數)。這是由於:

我們考慮一個n-bit的二進制補碼數a,最高位(第n-1位)是符號位。對於正數,有a[n-1]=0,將其擴展為w-bit時,第n-1位及其左邊的w-n位都為0。這個條件是充分必要的,我們可以反證出,如果前面最高的w-n+1位中(除了最高位),某一位有1,則一定不能用n-bit的二進制補碼數表示出。

而對於負數,有a[n-1]=1,將其擴展為w-bit時,第n-1位及其左邊的w-n位都為1。這個條件同樣是充分必要的,在此不再進行說明。

由此我們可以得到解法:(1)用掩碼屏蔽掉x的高w-n位,即取得00…x[n-1]x[n-2]…x[0]。(2)然后將x[n-1]擴展到最高的w-n位中,得到x’=x[n-1]x[n-1]…x[n-1]x[n-2]…x[0]。(3)最后return x’==x即可。

步驟:

(1)y= (~((~0)<<n-1<<1))&x(分兩次左移,為了處理n==w的情形)

(2)z=y>>n-1,z[0]是符號位,z==1為負,z==0為正就是符號位。

x’=((0-z)&(~0<<n-1<<1))|y

(3)return x’==x

 

2.71

A:packed_t是無符號數,而它包裝的4個字節都是有符號數,1byte的包裝在無符號數中的有符號數擴展后符號位並沒有擴展。例如,1byte的0xFF表示-1,而擴展成32位后應該為0xFFFFFFFF。而按照原函數中所示,擴展后結果是0x000000FF。

B:其實這個題可以將unsigned轉換成signed,然后利用算數右移來取得高位符號位的擴展。但是我們有另外一個解法:

unsigned x=word>>(bytenum<<3);

x<<=24;

x>>=24;

unsigned y=x>>7;

y<<=8;

return x-y;

這個解法不理解的話,自己動手找個示例畫一畫就理解其中的原理了。

2.72

A:sizeof返回size_t,實際上是無符號整型,而maxbytes-sizeof(val)則會隱式裝換為無符號數減法,所的結果始終是正數。測試條件>=0始終成立。

B:maxbytes>=0&&maxbytes>=sizeof(val)

 

2.73

編寫一個函數,當正溢出時,將結果設為Tmax,當負溢出時,將結果設置為Tmin。

首先,我們需要判斷是否發生了溢出:x、y同號,而與x+y異號時則發生了溢出:即(1)=((x^(x+y))&(y^(x+y))>>(w-1)。經過這個式子處理,發生溢出時,(1)式結果為1111….111,未發生溢出時,(1)式結果為0000….000。

然后,我們判斷發生的溢出是正溢出還是負溢出:即

(2)=(1)&(x>>(w-1)),正溢出時,(2)為0000…000;負溢出時,(2)為11111…111。

未發生溢出時,結果為x+y,而發生溢出時,(x+y)這部分結果需要屏蔽掉,代之以相應的Tmax或者Tmin。故最終的結果包含了(x+y)|(1)。當x+y沒有發生溢出時,這部分結果為x+y,而發生溢出時,這部分結果為1111…111。而當正溢出時,我們需要的結果是0111…111,負溢出時,我們需要的結果是1000…000,即我們需要在(x+y)|(1)的基礎上減去相應的值:

當未發生溢出時,減去0

當正溢出時,減去1000…000

當負溢出時,減去0111…111

綜上所述,我們可以得到需要減去的那部分的值的表達式:

(1)&((1<<(w-1))^(2))。故最終的結果為:

(x+y)|(1)- (1)&((1<<(w-1))^(2)),其中(1)、(2)的表達式如上述分析中所示。

2.74

這道題是測試減法是否發生了溢出。如果我們一下子不好考慮的話,先分析在減法中哪些情況會發生溢出。設x-y=z,

當x>0,y<0,z<=0時會發生溢出;

當x<0,y>0,z>=0時會發生溢出。

即x、y異號,並且x與z異號時會發生溢出。特別注意z=0的情形。

最后的答案可以寫為:

return (x^y!=0)&&(z==0||x^z!=0)

 

2.75

設兩個bit串的signed表示為x’、y’,unsigned表示為x,y。根據本章所學,有x’=x+x[w-1]*2w,y’=y+y[w-1]*2w

x’*y’ = (( x+x[w-1]*2w) * (y+y[w-1]*2w) mod 2w

       =(x*y+(y*x[w-1]+x*y[w-1])*2w+x[w-1]*y[w-1]*2w) mod 2w

注意上式,(x*y)的高w位在(x*y)中計算出,x’*y’相對於x*y高w位所多出的數是y*x[w-1]+x*y[w-1]。故經過unsigned_high_prod計算出的結果,需要加上x(當y為負時),與y(當x為負時),就是signed_high_prod的結果。

 

2.76

A:a<<2+a

B:a<<3+a

C:a<<5-a<<1

D:a<<3-a<<6

 

2.77

書中P130頁有原理敘述。對於本題,我們首先需要判斷x是正數還是負數:

int a=x>>w-1;

a==111….111則為負數,a==000….000則為正數。正數時不用額外考慮,而負數時需要加上一個bias偏差來向0舍入:

int result=((y-1)&a+x)/y,y=1<<k,帶入之后即可得到最終答案。

2.78

5*x/8:

x<<2+x得到5*x,而除以8的操作需要利用2.77題的結果,最終結果是: return divide_power(x<<2+x,3)。

 

2.79

這道題與2.78的唯一區別是計算5x/8時不能發生溢出。因為5x/8=x/2+x/8。當x為負數時,我們需要求得5x/8向上舍入的結果,在博客上我們使用celling(x)來表示x向上舍入的正數。我們可以通過對幾個x的取值進行驗證,得到如下關系:

celling(5x/8)=celling(x/2)+celling(x/8)+1,當x=-5-8k或-7-8k,k=0,1,2…

celling(5x/8)=celling(x/2)+celling(x/8),其他情況。

       由2.77可以得到:

celling(x/2)=divide_power2(x,1)

celling(x/8)=divide_power2(x,3)

下面判斷x=-5-8k或者x=-7-8k。負值不好判斷,而正值比較好判斷,即:-x=5+8k,后3位為101。而-x=-7+8k,后三位為111。

故我們可以得到,

celling(5x/8)= celling(x/2)+celling(x/8)+(-x&0x5||-x&0x7)&0x1。

而當x為正數時,我們也需要經過相似的判斷,經過計算,可以知道當x=5+8k或x=7+8k時,同樣需要多加一個1。

故而,最后總的式子為:

celling(5x/8)= celling(x/2)+celling(x/8)+(-x&0x5||-x&0x7||x&0x5||x&0x7)&0x1

2.80

A:~((1<<n)-1),注意此時0<n<w。

B:(1<<(n+m)-1)-(1<<m-1)=1<<(n+m)-1<<m。

 

2.81

A:錯誤,考慮x=0,y=1<<31,x>y,而-x>-y。

B:正確。數學中常用的分配律、結合律即可證明,<<5即乘以32。

C:錯誤。~x+~y=(-x-1)+(-y-1)=-(x+y)-2,不等於-(x+y)-1

D:正確的。signed與unsigned的加法計算在本質上是一致的。

E:正確。右移會向小值舍入。

 

2.82

A:我們要求出那個無窮循環的串的數學公式表示。令其為V,則有

V<<k=V+Y(高等數學知識)

       則V=Y/(2k-1)

B:(a) 1/7 (b) 0.6 (c) 1/9

 

2.83

浮點數最高位為符號位。先處理-0、+0比較的情況:

ux<<1==0&&uy<<1==0

x為正,y為正:

sx==0&&sy==0&&ux>=uy

x為負,y為負:

sx==1&&sy==1&&ux<=uy

x為正,y為負:

!sx&&sy

以上式子使用||連接起來就是最終答案。

 

2.84

A:f=0.01,M=1.01,E=2。注意到E=2而原始的exp-bias=E,exp=1+2k-1

B:f=0.11…1,M=1.11…1。而注意要精確表示最大的奇數。f的最后一個1一定要保留,所以E最大為n。此時,原始的exp=n+2k-1-1。

C:最小的正normalized值的表示為

2^(2-2k-1)。要求它的倒數,則指數部分應該是2^(2k-1-2),以使得乘積為1。而數值部分,則為0。

 

2.91

unsigned sign=f>>31;

unsigned exp=f>>23&0xFF;

unsigned frac=f&0x7FFFFF;

if(!(exp^0xFF==0&&frac!=0)) f|=1<<31;

return f;

 

2.92

unsigned sign=f>>31;

unsigned exp=f>>23&0xFF;

unsigned frac=f&0x7FFFFF;

if(!(exp^0xFF==0&&frac!=0)) f+=1<<31;

return f;

 

2.93

unsigned sign=f>>31;

unsigned exp=f>>23&0xFF;

unsigned frac=f&0x7FFFFF;

 

if(exp^0xFF==0&&frac!=0) return f;

else if(exp>0) exp=exp-1;

else {

       //round to even

       if(frac&0x1!=0&&(frac>>1)&0x1==1)

              frac=frac>>1+1;

       else frac=frac>>1;

}

return (sign<<31)|(exp<<23)|frac;

 

2.94

unsigned sign=f>>31;

unsigned exp=f>>23&0xFF;

unsigned frac=f&0x7FFFFF;

 

if(exp^0xFF==0&&frac!=0) return f;

else if(exp&0xFE!=0&&exp!=0) exp=exp+1;

else if(exp==0)

{

       if(frac>>23&0x1==0) frac<<1;

       else

{    

       exp=1;

       frac<<1;

}

}

return (sign<<31)|(exp<<23)|frac;

 

2.95

i是整數,故而E部分的指數最小也為0+127=127。不包括E為0的情況。我們首先判斷最高位的1在哪個位置。

//符號位

int sign=i>>31;

 

//求i的絕對值

if(!sign) i=~i+1;

 

//求數值位最高位的1所在位置

int pos=0;

if(i>>16) {pos+=16; i>>=16;}

if(i>>8) {pos+=8; i>>=8;}

if(i>>4) {pos+=4; i>>=4;}

if(i>>2) {pos+=2; i>>=2;}

if(i>>1) {pos+=1; i>>=1;}

 

exp=pos+127;

//屏蔽掉數值部分為1的最高位(第pos位),這一位在浮點數中是隱式顯示表示的。

i=~(1<<pos)&i;

//第pos位要左移或者右移到第23位,使得低23位是frac部分。

if(i>>23)

{

       frac=i>>(pos-23);

       //最低(pos-23)位要看是否需要round to even,並且pos!=23

if(!(pos^0x17)&&(((1<<(pos-23)-1)&&i)^(1<<(pos-23-1)))==0))

{

       //round to even

       if(frac&0x1) frac+=1;

       //發生溢出時,需要進位

       if(frac==0) exp+=1;

}

}

else

{

       frac=i<<(23-pos);

}

return (sign<<31)|(exp<<23)|frac;

2.96

這道題還是比較繁瑣,解法可類比2.95,但不再具體寫代碼了。在求解的時候,首先不考慮符號位,先求絕對值的表示,設這個絕對值為ret。

由於采用了round to zero的策略,故而不需要考慮“入”,只需要考慮“舍”即可。

(1)exp<0+127,ret=0

(1)exp=0+127,ret=1;

(2)exp=255,frac!=0時,ret=0x80000000

(3)exp等於其他值時,我們先把frac部分提出來,在第23位變為1,這是因為在float表示中,這個整數部分的1被隱藏了,我們求解int的時候,需要將其加上。此時,小數點在第23位的右側。

當exp=127時,frac>>=23;

當exp=128時,frac>>=22;

……

當exp=150時,frac>>=0;

當exp=151時,frac<<=1;

當exp=152時,frac<<=2;

……

當exp=157時,frac<<=7;

之后,再左移的話,就會溢出。也即exp>157時,ret=0x80000000。

最后,如果是正數的話,直接拼裝一下。而如果是負數的話,先按正數拼接,然后變反即可。不需要考慮Tmin的原因是float沒有足夠的精度來表示Tmin,故參數給出的f不可能被表示成Tmin。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM