深入理解计算机系统(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;
}
发布了33 篇原创文章 · 获赞 3 · 访问量 619

猜你喜欢

转载自blog.csdn.net/qq_43647628/article/details/104517889
今日推荐