CSAPP Lab1-datalab

前言

CSAPP 这本书可以说是程序员必读专业书籍了。这本书以一个 “Hello World” C语言程序为起点,通过跟踪整个"Hello World"程序的生命周期,来讲解系统为了执行该程序都做了哪些事情。就像书名一样,学习本书,能让我们对计算机系统有一个更深入的理解。
在学长的推荐下,我开始啃 CSAPP(《深入理解计算机系统》) 这本书,读了前两章,感觉难度还是不小的,但收获也是挺多的,尤其是 “计算机抽象等级” 和 “浮点数” 这两块知识,学了之后有种豁然开朗的感觉。 这本书还有一个特点就是配套的几个实验了,俗话说“纸上得来终觉浅”,配合着实验食用这本书,真是嘎嘣脆,事半功倍。
Lab1 对应的就是第二章的知识点,写起来挑战性还是很大的。

Lab1 Solution

Lab1 主要就是用 C 语言在一些操作限制的情况下实现题目要求。
主要的限制是操作符和操作数。还有一点要注意的就是变量的声明必须放在函数的最前面。
整个实验还是很有难度的,我们除了要对正数,浮点数二进制运算要非常熟悉外,还有很多 trick需要我们去思考,毕竟操作数是有限的,我们进行操作的时候要尽可能的“节约”。
每写完一道题都可以用 btest 测试一下,但是这个只能测试结果是否正确,操作是否合规无法测试。在所有题目都完成后,可以运行 Driver.pl 对程序进行一个完整的测试,如果能得到所有的分数,那么恭喜你,这个实验就算是圆满完成了。(当然如果想追求极致,可以尝试用更少的操作数来完成实验)

bitAnd

x&y = ~~(x&y) = (x|~y)

/* 
 * bitAnd - x&y using only ~ and | 
 *   Example: bitAnd(6, 5) = 4
 *   Legal ops: ~ |
 *   Max ops: 8
 *   Rating: 1
 */
int bitAnd(int x, int y) {
	return ~(~x | ~y);
}

getByte

将要取出的八位移动到最右端,并和 0xff 做且运算。

/* 
 * getByte - Extract byte n from word x
 *   Bytes numbered from 0 (LSB) to 3 (MSB)
 *   Examples: getByte(0x12345678,1) = 0x56
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 6
 *   Rating: 2
 */
int getByte(int x, int n) {
	return (x >> (n << 3)) & 0xff;
}

logicalShift

先进行算数右移,再将补充的高位置为0。

/* 
 * logicalShift - shift x to the right by n, using a logical shift
 *   Can assume that 0 <= n <= 31
 *   Examples: logicalShift(0x87654321,4) = 0x08765432
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 20
 *   Rating: 3 
 */
int logicalShift(int x, int n) {
	int result = (x >> n) & (~(((1 << 31) >> n) << 1));
  	return result;
}

bitCount

这道题是难度最大的一道了。
我们需要用二分的思想,先两位两位地统计1的个数,并将答案存在这两位上。然后四位四位地统计,接着是八位,直到统计完三十二位。
统计的时候运用错位相加的方法,需要注意的是在移位之前要将无关位置0,以免无关位相加对结果造成影响。

/*
 * bitCount - returns count of number of 1's in word
 *   Examples: bitCount(5) = 2, bitCount(7) = 3
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 40
 *   Rating: 4
 */
int bitCount(int x) {
	int tmp = 0x55 | (0x55 << 8);
	int mask1 = tmp | (tmp << 16);
	int tmp1 = 0x33 | (0x33 << 8);
	int mask2 = tmp1 | (tmp1 << 16);
	int tmp2 = 0x0f | (0x0f << 8);
	int mask3 = tmp2 | (tmp2 << 16);
	x = (x & mask1) + ((x >> 1) & mask1);
	x = (x & mask2) + ((x >> 2) & mask2);
    x = (x & mask3) + ((x >> 4) & mask3);
	x = x + (x >> 8);
	x = x + (x >> 16);
	
	return x & 0x3f;
}

bang

如果x是0的话,0|0的符号位仍为0;x非0的话,x|x 的符号位为1。

/* 
 * bang - Compute !x without using !
 *   Examples: bang(3) = 0, bang(0) = 1
 *   Legal ops: ~ & ^ | + << >>
 *   Max ops: 12
 *   Rating: 4 
 */
int bang(int x) {
	int minusx = ~x + 1;
	int sign = (x | minusx) >> 31;
	
	return (~sign) & 0x1;
}

tmin

久违的送分题。

/* 
 * tmin - return minimum two's complement integer 
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 4
 *   Rating: 1
 */
int tmin(void) {
	return 1 << 31;
}

fitsBits

这道题需要注意的就是符号位了。之所以向移动 (32-n) 位,而不是 (31-n) 位就是为了留出符号位。

/* 
 * fitsBits - return 1 if x can be represented as an 
 *  n-bit, two's complement integer.
 *   1 <= n <= 32
 *   Examples: fitsBits(5,3) = 0, fitsBits(-4,3) = 1
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 15
 *   Rating: 2
 */
int fitsBits(int x, int n) {
	int shiftNumber = 32 + (~n + 1);
	return !((x << shiftNumber >> shiftNumber) ^ x);
}

divpw2

这题难点在于舍入。正数直接右移 n 位即可,负数则需要加一个偏移量再右移。
还有一点需要注意的就是掩码的求取了,需要一些技巧,毕竟操作数是有限的,还要节约使用。

/* 
 * divpwr2 - Compute x/(2^n), for 0 <= n <= 30
 *  Round toward zero
 *   Examples: divpwr2(15,1) = 7, divpwr2(-33,4) = -2
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 15
 *   Rating: 2
 */
int divpwr2(int x, int n) {
	int signx = x >> 31;
	int mask = (1 << n) + (~0);	
	int bias = signx & mask;
	return (x + bias) >> n;		
}

negate

送分题。

/* 
 * negate - return -x 
 *   Example: negate(1) = -1.
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 5
 *   Rating: 2
 */
int negate(int x) {
	return ~x + 1;
}

isPositive

这道题难点在于0的判断。0有一个非常特殊的性质就是 !0 = 1,利用0的这个特点,此题就不难解出了。

/* 
 * isPositive - return 1 if x > 0, return 0 otherwise 
 *   Example: isPositive(-1) = 0.
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 8
 *   Rating: 3
 */
int isPositive(int x) {
	return !((x >> 31) | (!x));
}

isLessOrEqual

这道题相当于枚举 x<=y 的所有情况:

  1. x为负数,y 为正数
  2. x 等于 y
  3. x 和 y 符号相同,并且 y-x 为正数
/* 
 * isLessOrEqual - if x <= y  then return 1, else return 0 
 *   Example: isLessOrEqual(4,5) = 1.
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 24
 *   Rating: 3
 */
int isLessOrEqual(int x, int y) {
	int signx = (x>>31) & 0x1;
	int signy = (y>>31) & 0x1;
	int tmp = signx ^ signy;
	int tmp1 = tmp & signx;
	int y_minus_x = y+(~x+1);
	return tmp1 | (!(x^y)) | (!tmp & !(y_minus_x&(1 << 31)));
}

ilgo2

求以2为底,x 的对数。
这道题算是难题之一了,其原理和 bitCount 有些类似,都是用二分的思想,但这道题是求出最高位的1。
通过二分整数的所有位,找出最高位1的位置。

/*
 * ilog2 - return floor(log base 2 of x), where x > 0
 *   Example: ilog2(16) = 4
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 90
 *   Rating: 4
 */
int ilog2(int x) {
	int bitsNumber = 0;
	bitsNumber = (!!(x >> 16)) << 4;
	bitsNumber += (!!(x >> (bitsNumber + 8)) << 3);
	bitsNumber += (!!(x >> (bitsNumber + 4)) << 2);
	bitsNumber += (!!(x >> (bitsNumber + 2)) << 1);
	bitsNumber += (!!(x >> (bitsNumber + 1)));
	return bitsNumber;
}

float_neg

从这道题开始就进入了浮点数的练习,这部分需要我们对 IEEE 浮点数编码先回顾一下。
这道题难点就在于 NaN 的判断,如果 uf 是 NaN,需要原值返回。

/* 
 * float_neg - Return bit-level equivalent of expression -f for
 *   floating point argument f.
 *   Both the argument and result are passed as unsigned int's, but
 *   they are to be interpreted as the bit-level representations of
 *   single-precision floating point values.
 *   When argument is NaN, return argument.
 *   Legal ops: Any integer/unsigned operations incl. ||, &&. also if, while
 *   Max ops: 10
 *   Rating: 2
 */
unsigned float_neg(unsigned uf) {
	unsigned result = uf ^ (1 << 31);
	int tmp = 0xff << 23;
	if((uf & (tmp|(0xff << 15)|(0xff << 7)|0xff)) > tmp){
		result = uf;
	}	
	return result;
}

float_i2f

这道题完全就是考察队 IEEE 浮点数编码的熟悉程度了。第一遍写的时候怎么都过不了,后来发现是望考虑舍入了。除此之外这道题操作数也是一个难点,需要用到不少小 trick 来精简操作数。

/* 
 * float_i2f - Return bit-level equivalent of expression (float) x
 *   Result is returned as unsigned int, but
 *   it is to be interpreted as the bit-level representation of a
 *   single-precision floating point values.
 *   Legal ops: Any integer/unsigned operations incl. ||, &&. also if, while
 *   Max ops: 30
 *   Rating: 4
 */
unsigned float_i2f(int x) {
	int first1 = 31;
	int frac = 0;
	int flag = 0;
	unsigned tmp = 0;  
	int exp = 0;
	int mask = (0xff << 15)|(0xff << 7)|(0x7f);
	int mask1 = 1 << 31;
	int sign = x & mask1;
	if(x < 0){
		x = -x;
	}
	if(x == 0)
		return 0;
	
	if(x > 0){
		while(!(x >> first1)){
			first1 = first1 - 1;
		}	
		if(first1 > 23){
			frac = (x >> (first1 - 23)) & mask;
			tmp = x << (55 - first1);
			if(tmp > mask1)
				flag = 1;
			else if(tmp == mask1){
				if(frac & 0x1)
					flag = 1;
			}
		}
		else{
			frac = (x << (23 - first1)) & mask;
		}
	}
	exp = 127 + first1;
	return sign + (exp << 23) + frac + flag;
}

float_twice

最后一道题是浮点数二倍乘。无穷大和 NaN 就直接返回原值就好,规格化数阶码直接加1,非规格化数尾数左移一位(移位后可能变为规格化数,但这并不影响答案,这也正是 IEEE 浮点数编码的巧妙之处,不用担心)。

/* 
 * float_twice - Return bit-level equivalent of expression 2*f for
 *   floating point argument f.
 *   Both the argument and result are passed as unsigned int's, but
 *   they are to be interpreted as the bit-level representation of
 *   single-precision floating point values.
 *   When argument is NaN, return argument
 *   Legal ops: Any integer/unsigned operations incl. ||, &&. also if, while
 *   Max ops: 30
 *   Rating: 4
 */
unsigned float_twice(unsigned uf) {
	int mask1 = 0xff << 23;
	unsigned result = 0;
	if((uf & mask1) == mask1){
		result = uf;
	}
	else if((uf & mask1) == 0x0){
		result = (uf & (1 << 31)) | (uf << 1);
	}
	else{
		result = uf + (1 << 23);
	}
	return result;
}

后记

之前只知道浮点数有误差,但并不知其所以然。有些数,比如0.1是无法用浮点数准确表示的,只能近似表示。int转 float,long long 转 double 由于尾数限制,也都会被舍入。总的来说,浮点数的精度由尾数位数决定,范围是由阶码位数决定的。
CSAPP 这本书真的不错,无论是书上的知识还是实验的内容干货都非常的多。学了前两章,收获还是挺大的,尤其是整数和浮点数的二进制表示这部分知识。但是感觉进度实在是太慢了,按照现在的速度,今年都不一定把这本书看完= =,接下来还是提高效率,抓紧时间把这本书啃完吧。

后后记

书啃完了,实验做了四个,实验代码放在了 github 上,但是后面三个因为太懒都没写博客了 QAQ。之后学习工作要及时笔记,以便回顾。

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值