深入理解計算機系統(csapp)家庭作業——第二章信息的表示和處理

  1. 編寫過程is_little_endian,當在小端法機器上編譯和運行時返回1,在大端法機器上編譯運行則返回0。
int is_little_endian()
{
	int i = 0;
	return  *(char*)&i; //整型指針轉換爲字節指針,每次指向一個字節
}
  1. 編寫一個C表達式,它生成一個字,由x的最低有效字節和y中剩下的字節組成,對於運算數x=0x89ABCDEF和y=0x76543210,就得到0x7654321EF
int main()
{
	unsigned  x = 0x89ABCDEF;
	unsigned  y = 0x76543210;
	unsigned  num = (x & 0xFF) | (y & ~0xFF);
	printf("%X", num);
}
  1. 假設我們將一個w位的字中的字節從0(最低位)到w/8-1(最高位)編號。寫出下面C函數的代碼,它會返回一個無符號值,其中參數x的字節i被替換成字節b
    如:replace_byte(0x12345678,2,0xAB)->0x12AB5678
unsigned replace_byte(unsigned x, int i, unsigned char b)
{
	char* x_char = (char*)&x;
	*(x_char + i) = b;
	return x;
}
  1. 如圖:
    在這裏插入圖片描述
printf("%d", !~x  || !x || !~(x | 0x00ffffff) || !(x & 0x000000ff));
  1. 編寫一個函數int_shifts_are_arithmetic(),在對int類型的數使用算數右移的機器上運行時這個函數生成1,而其他情況生成0。
int int_shifts_are_arithmetic()
{
	int i = -1;
	int i1 = i >> 4;
	char* i1_one = (char*)&i1;
	return *i1_one & 1;
}
  1. 如圖:
    在這裏插入圖片描述
//算術右移實現邏輯右移
unsigned sr1(unsigned x, int k)
{
	unsigned xsra = (int)x >> k;
	unsigned cmp = ~(-1 << (32 - k));
	return xsra & cmp;
}
//邏輯右移實現算術右移
int sra(int x, int k)
{
	unsigned xsra = (unsigned)x >> k;
	unsigned cmp = (-1 << (32 - k));
	return xsra | cmp;
}
  1. 寫出代碼實現以下函數:
/*
Return 1 when any odd bit of x equals 1;0 otherwise.
Assume w=32
*/
int any_odd_one(unsigned x)
{
	return !~(x | 0xAAAAAAAA);
}
  1. 寫出代碼實現如下函數
    思路:
    異或運算^
    現在將這堆數(偶數個)分成兩部分,前一半和後一半的數一 一對應進行異或運算,得到的結果再分成更少的兩部分,重複上一個步驟,直到只剩一個數,就是答案了。
    例如:1101,分成11和01兩部分,11異或01=10,10再分成1和0,1異或0=1。得到結果
/*
Return 1 when x contains an odd number of 1;0 oherwise.
判斷二進制中1的個數是否爲奇數
Assume w=32
*/
int odd_ones(unsigned x)
{
	x ^= x>>16;
	x ^= x>>8;
	x ^= x>>4;
	x ^= x>>2;
	x ^= x>>1;
	return x&1;  //最終最低有效位即答案
}
  1. 如圖:
    在這裏插入圖片描述
    A.一次左移位數應該小於計算機位數
    B.一次左移分兩次
    C.一次左移分三次
  2. 寫出具有如下原型函數的代碼
/*
Mask with least signficant n bits set to 1
Examples: n=6-->0x3F
*/
int lower_one_mask(int n)
{
	return ~(-1<<n);
}
  1. 寫出具有如下原型函數的代碼
/*
Do rotating left shift 
Examples when x = 0x12345678 
n=4 ->0x23456781 ,n =20->0x67812345
*/
unsigned rotate_left(unsigned x,int n)
{
	return x<<n | x>>32-n;
}
  1. 如圖:
    在這裏插入圖片描述
    A.前任的代碼會擴展成無符號數,而不是有符號數
    B
int xbyte(packed_t word,int bytenum)
{
	return ((int)(word<<(24 - bytenum<<3)))>>24;
}
  1. 如圖:
    在這裏插入圖片描述
    A.無符號數參與運算得到的結果是無符號數,永遠比0大
    B.強制轉換成int
  2. 寫出具有如下原型的函數代碼,同正常的補碼加法溢出的方式不同,當正溢出時,飽和加法返回TMax,負溢出時,返回TMin。飽和運算常常用在執行數字信號的處理的程序中
int saturating_add(int x, int y)
{
	//判斷是否溢出,溢出返回0xFFFFFFFF,不溢出返回0
	int result = (x ^ (x + y) & y ^ (x + y)) >> 31;
	//判斷是否爲正溢出
	int neg_result = (result << 31 & (x + y)) >> 31;
	//判斷是否爲負溢出
	int pos_result = (result << 31 & ~(x + y)) >> 31;
	//用位代替條件判斷語句的方法就是用若干個|,同一時間只有一項不爲0,一般都是&上一個值爲0x0或0xFFFFFFFF的值作爲判斷條件
	//用於若干個條件互斥,只有一個不爲0
	//&~result是保證溢出的話這一項爲0
	return (x + y) & ~result | 0xEFFFFFFF & neg_result | 0x80000000 & pos_result;
	//       不溢出返回x+y           正溢出返回TMax            負溢出返回TMin
}
  1. 寫出具有如下原型的函數的代碼:
//如果計算x-y不溢出,這個函數就返回1
int tsub_ok(int x,int y)
{
	//判斷是否溢出
	int result = (unsigned)(x^(x-y) & ~y^(x-y))>>31;
	return result;
}
  1. 如圖
    在這裏插入圖片描述
/*
	這個問題需要一步一步的進行推導
	T2Uw(x)我們把這種寫法稱爲補碼轉無符號數,那麼很容易得出:
	(2^w表示2的w次方,爲什麼當x<0時是這個結果呢,
	其實,補碼的負數就是把原來w-1之後的位的結果減去了最高一位的值,最高位的值就是2^w)
	if x < 0  => x + 2^w
	if x > 0  => x

	上邊的公式很簡單,但在使用的時候還要做判斷,顯然很不科學,我們可以認爲T2Uw(x)是一個函數
	接下來就想辦法推導出一個表達式來

	這裏省略了一系列的推導過程,得出了這樣一個結果"
	T2Uw(X)= X + X(w-1)2^w

	大家看看這個式子跟上邊的那個作用一樣,x的w-1位就是他的最高位,如果該位的值是1,那麼就相當於
	x<0的情況,否則就是另一種情況

	我們假設x`表示x的無符號值
	X` = X + X(w-1)2^w

	我們假設y`表示x的無符號值
	Y` = Y + Y(w-1)2^w

	那麼X` * Y` = (X + X(w-1)2^w) * (Y + Y(w-1)2^w)
	如果要把這個計算式展開會很麻煩,我們可以進一步抽象
	設a = X(w-1)2^w, b= Y(w-1)2^w
	則: X` * Y` = X*Y + X*b + Y*a + a*b

	我們假定有這樣一個函數,他的功能是取出無符號數的最高位uh(),因此上邊的式子變形爲:
	uh(X` * Y`) = uh(X*Y + X*b + Y*a + a*b)
				= uh(X*Y) + uh(X*b) + uh(Y*a) + uh(a*b)

	那麼X * b 也就是X*b= X*Y(w-1)2^w 他的最高位的值就是X*Y(w-1)2^w / 2^w => X*Y(w-1)
	那麼Y * a 也就是Y*a= Y*X(w-1)2^w 他的最高位的值就是Y*X(w-1)2^w / 2^w => Y*X(w-1)
	那麼a * b 也就是a*b= X(w-1)2^w * Y(w-1)2^w 他 / 2^w => 0

	===> uh(X` * Y`) = uh(X*Y) + X*Y(w-1) + Y*X(w-1)

	上邊推理的核心思想就是 無符號X`的補碼錶示:X + X(w-1)2^w 求高位的/ 2^w 操作
*/
unsigned unsigned_high_prod(unsigned x,unsigned y)
{
	int sx = (int)x;
	int sy = (int)y;
	return signed_high_prod(sx,sy)+x<<31*y+y<<31*x;
}
  1. 如圖:
    在這裏插入圖片描述

void *calloc(size_t nmemb,size_t size)
{
/*
	乘法和無符號加減法用如下方式判斷是否溢出
	all_size/nmemb!=size
	有符號加減法判斷最高位是否符合條件
	(x+y)& x | (x+y) & y
*/
	if(nmemb==0||size==0)
		return NULL;
	size_t all_size = nmemb*size;
	//處理溢出
	if(all_size/nmemb!=size)
		return NULL;
	void* p = malloc(all_size);
	memset(p,0,all_size);
	return p;
}
  1. 如圖:
    在這裏插入圖片描述
A:x<<4+x
B:x-x<<3
C:x<<6-x<<2
D:x<<4-x<<7
  1. 寫出具有如下原型的函數的代碼:該函數要用正確的舍入方式計算x/2k
/*
	c語言標準規定向0舍入
	在本函數中用右移代替,這樣做會導致向下舍入,即需要調整x爲負的情況
*/
int divide_power2(int x,int k)
{
	int sum = x>>k;
	//條件爲x<0 且 x的前k位不爲0
	sum += x>>31 && (x&~(-1<<k));
	return sum;
}
  1. 寫出函數mul3div4的代碼,對於整數參數x,計算3x/4,但是要遵循位級整數編碼規則。你的代碼計算3x也會產生溢出
//不用處理溢出,要處理爲x負的舍入
int mul3div4(int x)
{
	//將負號保留
	int negflag = x >> 31;
	//先算乘法
	int x1 = (x << 1) + x;
	//再算除法
	int x2 = x1 >> 2;
	//處理舍入
	x2 += negflag && (x1 & ~(-1 << 2));
	return x2;
}
  1. 寫出函數threefourths的代碼,對於整數參數x,計算3/4x的值,向零舍入。它不會溢出。函數應該遵循位級編碼規則。
/*
大體方向是先右移在左移
但是這樣會導致舍入判斷丟失
所以應該先將做出舍入的判斷
*/
int threefourths(int x)
{
	int negflag = x << 31;
	int x1 = (x << 1) + x;
	int isneg = negflag && (x1 & ~(-1 << 2));
	int x2 = x >> 2;
	int x3 = (x2 << 1) + x2;
	x3 += isneg;
	return x3;
}
  1. 如圖:
    在這裏插入圖片描述
    A.1w-k0k
    B.0w-k-j1k0j
//Assume w = 32
int A(unsigned k)
{
	return -1<<k;
}
int B(unsigned k,unsigned j)
{
	return (-1<<j) & ~(-1<<(k+j));
}
  1. 如圖
    在這裏插入圖片描述
	A. 不總爲1:
	我們要知道兩個正負都一樣的數
	一個是0,是TMin
	所以當x=0,y=TMin時
	(x<y)==(-x>-y)不成立
	B.總爲1
	C.總爲1
	首先要知道~x+x=-1總是成立的
	~x+~y+1 = -x-1-y-1+1=-x-y-1
	~(x+y) = -x-y-1 == ~x+~y+1
	D.總爲1
	注意無符號數和有符號數加減乘除的位級等效性
	E.總爲1
	向右移再左移可能導致位的丟失
	所以小於等於1是正確的
  1. 如圖:
    在這裏插入圖片描述

    A.
    等比數列求和a1*(1-q^n)/(1-q)
    a1=Y/(2^k) q=1/(2^k)
    假設n非常大,1-q^n可看作1
    a1/(1-q)
    所以通項公式是Y/(2^k-1)
    B.
    y=101 5/7
    y=0110 2/5
    y=010011 19/63
    
  2. 如圖:
    在這裏插入圖片描述

int float_le(float x,float y)
{
	unsigned ux = f2u(x);
	unsigned uy = f2u(y);
	unsigned sx = ux>>31;
	unsigned sy = uy>>31;
	//一共三種可能性
	return sx&~sy || (sx&sy)&&(ux>=uy) || (~sx&~sy)&&(ux<=uy)
//       x爲負y爲正       x,y都爲負              x,y都爲正
}
  1. 如圖:
    在這裏插入圖片描述
    知識點:
    (1)浮點數的一般表示
    在這裏插入圖片描述
    (2)浮點數的位編碼
    在這裏插入圖片描述
    A:數7.0->111.000
    尾數M爲1.11,小數f爲0.11
    階碼E爲2,bias爲2k-1-1,e爲E+bias = 2k-1+1
    位表示:0|100…001|11000…
    B:
    尾數M爲1.111…1,小數f爲0.11…1
    階碼E爲n(前提是k足夠大),e = E+bias = 2k-1-1+n
    位表示:0|e|11111…111
    C:
    最小的規格化數的E爲2-2k-1,其倒數:
    尾數M爲1.000…000,小數f爲0.000…00
    階碼E爲2k-1-2,e = E+bias = 2k-1-k = 2k-3
    位表示:0|e|00…00000
  2. 與Inter兼容的處理器也支持“擴展精度”浮點形式,這種格式具有80位字長,被分成1個符號位、k=15個階碼位、1個單獨的整數位和n=63個小數位。整數位是IEEE浮點表示中隱含位的顯式副本。也就是說,對於規格化的值它等於1,對於非規格化的值它等於0。填寫下表:
描述 二進制
最小的正非規格化數 0-0…0-0-0…001 2-61-214
最小的正規格化數 0-0…01-1-0…00 22-214
最大的規格化數 0-11…10-1–11…1 (2 - 2(-63)) * 2(214 - 1)
  1. 如圖:
    在這裏插入圖片描述
描述 Hex M E V D
-0 0x8000 0 -14 -0 -0.1
最小的>2的值 0x4001 1025/1024 1 1025*2-9 1025.001953
512 0x6000 0 24 512 512
最大的非規格化數 0x03FF 1023/1024 -14 1023*2-10 0.999023
-∞ 0xFC00 —— —— -∞ -∞
十六進制表示爲3BB0 3BB0 123/64 -1 123 * 2-7 0.960938

非規格化數的E爲1-bias
D是用printf的規範%f打印,默認爲6位

  1. 考慮下面兩個基於IEEE浮點格式的9位浮點表示
    (1)格式A
    有1個符號位,5個階碼位,3個小數位,bias爲15
    (2)格式B
    有1個符號位,4個階碼位,4個小數位,bias爲7
    將下面表格A->B,如果要舍入,向+∞舍入。
0 10110 101 208 0 1110 1010 208
1 00111 110 -7/210 1 0000 0111 -7/2-10
0 00000 101 5/217 0 0000 0000 0
1 11011 000 -4096 1 1111 0000 -∞
0 11000 100 768 0 1111 0000 +∞

第二行中,要住轉換的B格式規格化最小能表示-2-6,所以要將A格式轉成格式化的B
第三行中,要轉換的B格式最小能表示1/210,故向零舍入
第四行中,B格式最大可表示416遠遠小於4096,向-∞舍入
第五行中,416也小於768,向+∞舍入

  1. 如圖:在這裏插入圖片描述
    補充知識點:
    float符號、階數、尾數分別爲1,8,23
    double符號、階數、尾數分別爲1,11,52
A:int->float會引起舍入,double->int會引起溢出+∞,但是這個double是int轉換的,所以表達式是成立的
B:左邊是double加減法,右邊是int加減法,假如右邊溢出而左邊沒有溢出則等式不成立
如:x = INT_MAX,y = -1,int會溢出
C:雖然說浮點數的加減乘除不滿足結合律
但此時的double是由int轉換而來,數值較小,不會捨去,故等式成立
D:乘法會導致溢出,不成立
	int x = 0xEFFFFFFF;
	int y = 0xEFFFFFFF-1;
	int z = 0xEFFFFFFF-2;
得到的結果不一樣
E:不成立
dx = 1,dz = 0
dx/dx = 1,dz/dz=NaN
  1. 如圖:
    在這裏插入圖片描述
float fpwr2(int x)
{
	unsigned exp,frac;
	unsigned u;
	if(x<-149)  //Too small Return 0.0
	{	
		exp = 0;
		frac = 0;
	}
	else if(x<-126) //非規格化結果
	{
		exp = 0;
		frac = 1<<(x+149);
	}
	else if(x<128)  //規格化結果
	{
		exp = 127 + x;
		frac = 0;
	}
	else
	{
		exp = 0xFF;
		frac = 0;
	}
	u = exp<<23 | frac;
	return u2f(u);
}
  1. 如圖:
    在這裏插入圖片描述

    A:表示的二進制小數爲:3.141593
    B:是11.001001001......
    C:第一個近似值是11.0010010000111...,第9位開始
    
  2. 如圖:
    在這裏插入圖片描述

float_bits float_negate(float_bits f){
	unsigned sign = f>>31;
	unsigned exp  = f>>23 & 0xFF;
	unsigned frac = f & 0x7FFFFF;
	if(exp==0xFF&&frac!=0)
		return (sign<<31) | (exp<<23) | frac;
	return (~sign<<31) | (exp<<23) | frac;
}
  1. 如圖:
    在這裏插入圖片描述
float_bits float_negate(float_bits f){
	unsigned sign = f>>31;
	unsigned exp  = f>>23 & 0xFF;
	unsigned frac = f & 0x7FFFFF;
	if(exp==0xFF&&frac!=0)
		return (sign<<31) | (exp<<23) | frac;
	return (0<<31) | (exp<<23) | frac;
}
  1. 如圖:
    在這裏插入圖片描述
float_bits float_twice(float_bits f){
	unsigned sign = f>>31;
	unsigned exp  = f>>23 & 0xFF;
	unsigned frac = f & 0x7FFFFF;
	if(exp==0xFF&&frac!=0)
		return (sign<<31) | (exp<<23) | frac;
	//如果是非規格化數
	if(exp == 0)
	{
		/*這裏分爲兩種情況;
		  第一種:左移後沒有超過1,此時沒有任何問題
		  第二種:左移後超過1了,此時要將浮點數規格化,那麼這裏爲什麼直接左移就行了呢?
		  首先,左移之後會將frac的最高有效位進位到exp的最低有效位,這時發現規格化後和
		  非規格化的E都是等於1-bias,這就是爲什麼非規格化的E要設置成1-bias而不是-bias,
		  它實現了非規格化到規格化的平滑過渡
		  然後,frac部分代表的就不是0.小數位,而是1.小數位了,所以最終結果是正確的
		*/
		frac<<1;
	}
	//如果是非規格化數
	if(exp!=0 && exp!=0xFF)
	{
		//當浮點數沒有達到最大的時候
		exp += 1;
		//如果加了後達到了飽和
		if(exp>=0xFF)
			frac = 0;  // 將浮點數置爲+∞
	}
	return (sign<<31) | (exp<<23) | frac;
}
  1. 上題的2改爲0.5
//整數補碼除法要注意的是C語言規定向0舍入,而右移後是向下舍入,x爲負數的時候需要注意是否要加1
//浮點數除法使用偶數舍入,
/*這裏我們考慮舍入,比如 f = 0 000…001 XXX…XYZ 進行右移變爲 0 000…000 1XX…XXY(Z),題目要求偶
  數舍入,需要看 Z 位,如果 Z 是 1,則需要舍入。偶數舍入要使 Y 位(最低有效位)爲 0,如果 Y 是 1,
  那麼就 f 就要加 1;如果 Y 是 0,直接舍掉 Z。
*/
float_bits float_half(float_bits f){
	unsigned sign = f>>31;
	unsigned exp  = f>>23 & 0xFF;
	unsigned frac = f & 0x7FFFFF;
	if(exp==0xFF&&frac!=0)
		return (sign<<31) | (exp<<23) | frac;
	if(exp==0) //非規格化右移
	{
		int isNeedRounding = frac && 0x01;
		//需要舍入的情況
		if(isNeedRounding)
		{
		 	frac = frac>>1;
		 	int isNeedPlus = -(frac & 0x01);
		 	frac = (~isNeedPlus)&frac | isNeedPlus & (frac+1);
		}
		else
			frac = frac>>1;
	}
	if(exp!=0&&exp!=0xFF)
	{	
		exp -= 1;
		if(!exp)
		{
			frac = frac>>1;
		 	int isNeedPlus = -(frac & 0x01);
		 	frac = (~isNeedPlus)&frac | isNeedPlus & (frac+1);
		 	frac = frac & 0x100000;
		}
	}
	return (sign<<31) | (exp<<23) | frac;
}
  1. 實現將float轉換成int的函數
/*
	一個整數的正常表示形式應爲XXXX
	如果小於0,就是0.XXXXX
	這時直接將後面的一串值放入frac
	如果大於0,我們先將其轉換爲二進制形式1.XXXX*2^E,XXXX的長度爲E
	首先將E+bias=e存入exp
	然後將前面的1丟掉(因爲用了規格化),將後面的XXX存入frac:
	如果可以放入就放入後後面補0
	如果放不下就只放23位
	這題是浮點數轉整數
	所以首先算出E = exp -bias
	然後取出frac對應E位的數,前面加上去掉的1
*/
int float_f2i(float_bits f) {
	unsigned sign = f >> 31;
	unsigned exp = f >> 23 & 0xFF;
	unsigned frac = f & 0x7FFFFF;
	if (exp == 0xFF && frac != 0)
		//dosomething'
		if (exp == 0)
			return 0;
	unsigned bias = 127;
	unsigned E = exp - 127;
	int i = 0;
	//如果XXXX小於23
	if(E<=23)
		i = frac >> (23-E) | 1 << E;
	//如果XXXX大於23
	else
		i = frac << (E-23) | 1 << E;
	if (sign)
	{
		i = -i;
	}
	if ((exp == 0xFF && frac != 0) | i>0xEFFFFFFF | i<0x80000000)
		return 0x80000000;
	return i;
}
  1. 實現將int轉換成float的函數
/*
	一個整數的正常表示形式應爲XXXX
	如果小於0,就是0.XXXXX
	這時直接將後面的一串值放入frac
	如果大於0,我們先將其轉換爲二進制形式1.XXXX*2^E,XXXX的長度爲E
	首先將E+bias=e存入exp
	然後將前面的1丟掉(因爲用了規格化),將後面的XXX存入frac:
	如果可以放入就放入後後面補0
	如果放不下就只放23位
*/
float_bits float_i2f(int i) {
	unsigned sign = 0;
	unsigned exp = 0;
	unsigned frac = 0;
	unsigned E = 0;
	unsigned bias = 127;
	int i_copy = i;
	if(i<0)
	{
		sign = 1;
		i = -i;
	}
	while(true)
	{
		i_copy = i_copy>>1;
		if(!i_copy)
			break;
		E += 1;
	}
	E-=1;
	exp = E + bias;
	//去1
	frac = i & (~(1<<E));
	if(E<=23)
		frac = frac << 23 - E;
	else
		frac = frac >> E-23;
	return (sign<<31) | (exp<<23) | frac;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章