CS:APP—我的Data lab奇妙冒险

Data lab的奇妙冒险

在学习完《深入理解计算机系统》(作家:Randal E.Bryant)一书的第2章—信息的表示和处理后,我对于数据在计算机底层的表示,存储,处理方式有了较为浅显的认知,对于位级运算,逻辑运算,移位运算也是一知半解,远远达不到运用的层次。所以,为了加深对知识的理解与掌握程度,选择CMU官网的Data lab实验来练练手,是个极好的选择。

我所完成的冒险有:

  1. bitXor
  2. Tmin
  3. isTmax
  4. allOddbits
  5. negate
  6. isAsciiDigit
  7. conditional
  8. isLessorEqual
  9. logicalNeg
  10. howmanybits

冒险注意事项:
1.整型的范围是 0 到 255(0xFF),不允许用更大
2.只能包含参数和局部变量
3.只能使用一元操作符 ! ~
4.只能使用二元操作符 & | + << >>
5.假设机器为32位

Talk is cheap,Let`go~

bitXor

  • 题目要求:使用~,&实现^
  • 允许操作:~,&
  • 最多操作符数目: 14

思考:仅仅使用按位取反和与运算,该如何达到异或的效果呢?
从异或的概念出发:相同为0,不同为1。而与运算,则是全1则1。
那么x&y就可以得到两个数都为1的位,同理,~x& ~y就可以得到都为0的位
这两者也就是x^y中为0的位!
那么结果就显而易见了,使其各自取反再进行与运算,就是x^y。

int bitXor(int x, int y) {
	  return ~(~x & ~y) & ~(x & y);
}

Tmin

  • 题目要求:返回最小的二进制补码
  • 允许操作: ! ~ & ^ | + << >>
  • 最多操作符数目: 4

思考:因为题目限制了我们只能使用0~255的数字,所以我们无法直接使用Tmin,也就是OX80000000。也就是1带31个0.
看见这个"<<"东西没有!看见1没有!那就很简单啦~

int tmin(void) {
  return 1<<31;
}

isTMax

  • 题目要求:如果x是最大的二进制补码,返回1;否则,返回0
  • 允许操作:! ~ & ^ | +
  • 最多操作符数目: 10

思考:最大的二进制补码为OX7FFFFFFF。他的特点是除了最高位,其他的都是1,得好好利用这一点。
全1有什么用呢?它已经接近数据的上限了,马上就要溢出了,那我们就顺水推舟,把他溢出:(x<<1)+2
这样,只有OX7FFFFFFF和OXFFFFFFFF会得到0。再进行一些处理和区别,就可以实现该函数了。

int isTmax(int x) {
  return (!((x<<1)+2))&(!(x>>31));
}

allOddbits

  • 题目要求:如果所有奇数位都为1则返回1;否则返回0
  • 允许操作:! ~ & ^ | + << >>
  • 最多操作符数目: 12

思考:首先,将不确定的转化为确定的——偶数位化为全1,也就是与OX55555555进行或运算。
那么问题来了,怎么用0~255的数字获得OX55555555呢?很简单,用OX55分别左移8,16,24再相加就行啦。
之后就很简单了,如果X的所有奇数位都为1,那么和OX55555555进行或运算,就可以得到OXFFFFFFFF。
想要得到1来表示它奇数位全1,只需要将OXFFFFFFFF先取反,再取非,就是我们要的结果。

int allOddBits(int x) {
	unsigned int a1 = 85;//a1=OX55
	unsigned int a2 = a1 + (a1 << 8) + (a1 << 16) + (a1 << 24);//a2=OX55555555
	return !~(x|a2) 
}

negate

  • 题目要求:返回-x
  • 允许操作:! ~ & ^ | + << >>
  • 最多操作符数目: 5

思考:不需要思考- -

int negate(int x) {
  return ~x+1;
}

isAsciiDigit

  • 题目要求:如果x是ascii码中的0~9(OX30-OX39),返回1;否则返回0
  • 允许操作: ! ~ & ^ | + << >>
  • 最多操作符数目: 15

思考:既然要在OX30-OX39中(也就是48~57),那么只要X减去下界符号为正,减去上界符号为负,就可以认定X在这个范围内。

int isAsciiDigit(int x) {
	return(!((x + ~48 + 1) >> 31)) & !!((x + ~58 + 1) >> 31);
}

conditional

  • 题目要求:实现x?y:z
  • 允许操作:! ~ & ^ | + << >>
  • 最多操作符数目: 16

思考:x非0,返回y,x为0,返回z。在不能使用条件控制语句的情况下,我们应该首先想到或运算的特殊性质:与0或为本身。
那么我们只要通过对x的是非0判断,来对y,z进行清零操作,就可以让另外一个值得以完整的输出。
还是将不确定的化为确定的:!x可以把各种花里胡哨的x之间变为0。
再根据题目要求,!x-1与-(!x)就可以让x在0或非0分别转化为OX0与OXFFFFFFFF,0且为0,1且为自己。
便可完成对y,z是自己还是清零的开关。

int conditional(int x, int y, int z) {
	return ((!x + ~1 + 1) & y) | ((~!x + 1) & z);
}

isLessorEqual

  • 题目要求: 如果x<=y,返回1,否则返回0
  • 允许操作:! ~ & ^ | + << >>
  • 最多操作符数目: 24

思考:乍一看,so easy!直接y-x,判断符号位不就完事了?
写到一半,emmm,这可是计算机系统课啊,不是高数课,会让你这么简单判断小于等于?不对劲。
再一想,wc,最最最基本的OVERFLOW都忘记了???
直接相减,将无法避免溢出的情况,从而导致判断错误。怎么相减符号不溢出呢?简单,同号相减,绝对不溢出。
异号?那只要x是负数不就行了。
所以思路明确:取x与y的符号位,并用异或判断是否异号。异号则判断x的符号,同号则计算y>x?

int isLessOrEqual(int x, int y) {
	int Sx = x >> 31;
	int Sy = y >> 31;
	int flag = Sx ^ Sy;//异号
	int S_ysubx = ((y + ~x + 1) >> 31) & 1;//y>x
	return (flag & (Sx & 1)) | ((~flag) & !S_ysubx);

logicalNeg

  • 题目要求:实现!运算符的功能
  • 允许操作: ~ & ^ | + << >>
  • 最多操作符数目: 12

思考:!将x区分为0为非0,那么就要找到他们之间的区别。
区别在哪呢?0的负数还是0,而非0则不行(除了特殊的OX80000000)
沿着这个思路走下去,只需要通过其符号位来判断:

  1. x=0::x与-x的符号位都为0。
  2. x=OX80000000:x与-x的符号位都为1。
  3. x为其他非0数:符号位相异。

0&0=0,1&1=1,0&1=0;
为了让情况1输出1,情况2,3输出0,利用0与OX80000000取负符号位不变,通过再次按位取反进行处理:
(~x & ~( ~x+1))>>31

int logicalNeg(int x) {		
	return ((~x & ~(~x + 1)) >> 31) & 1;
}

howmanybits

高难预警!!!(当然是对于我这个菜鸟来说)

  • 题目要求:返回将X表示为补码所需的最小有效位数。
  • 允许操作:! ~ & ^ | + << >>
  • 最多操作符数目: 90

思考:为精确的判断出需要多少位来表示这个数,就像诸葛亮通过十次机会判断出吴国大臣心里想的是0~1024中哪个数字一样,我们也可以通过二分法,不断缩小范围,从而得到最后的答案。

变量介绍:
shift i(i=1,2,4,8,16)用于存储每次进行二分法右移i位后,剩下的i位是否全0。若全0,则shift i为0,表示这i位不需要用来表示,若还有数字,则为i,表示这i位是需要用来表示的。
sum=2+shift i的总和,则为最后需要的位数。
op为操作数
t1判断是否为0,t2判断是否为-1。因为这两个是特殊的,0的每一位都没有值,但需要1位来证明自己的存在,而-1可以从很多个1减肥,只需要一个1来表示。

思路:
1.通过((!x) << 31) >> 31,可以使得x=0时,将t1设置为全1,((!~x) << 31) >> 31,使得x=OXFFFFFFFF时,可以将t2设置为全1.

2.负数因为前面会有很多个可以省去的1,影响二分法判断,故对其进行取反操作,使得二分法正常进行并且不会影响最后的答案,这里可以通过与符号位进行异或达到效果。即: op=x^(x>>31)

3.在进行完特殊处理之后,就可以对操作数op统一进行二分法处理了:!!(op>>i)可以巧妙判断高i位是否有值,并设置为1/0。
若!!(op>>i)为1,则说明低i位是表示该数所必须的,那么shift i=i,也就是1<<log 2 i,若shift i=0,则说明不足i位,改右移i/2位。

4.以此类推不断进行二分法,直到i=1

5.在二分法进行到i=1时,最后待处理的两位数op不可能00H,一定会是01或者10或者11中的一种情况。
所以在右移一位时,不管最后shift1是1还是0,所进行的右移丢弃的一位都是有值的/必须的,所以需要算上(sum+1)。
再者,因为负数取反的操作,还有一个符号位为0是隐形的,此处还需sum+1.
所以在进行完二分法后,还需要给sum加上2,就可以得到非特殊值表达所需要的位数。
也就是sum=2+shift i总和。

举个栗子:
X=298=0000000100101010H(前面省略16个0)

  1. op=x,op>>16为0,则shift16=0,op仍然等于x
  2. op>>8!=0,shift8=8,op=op>>8
  3. op>>4=0,shift4=0
  4. op>>2=0,shift2=0
  5. op>>1=0,shift1=0
  6. sum=2+8=10,298也确实是0100101010H。
int howManyBits(int x) {
	int shift1, shift2, shift4, shift8, shift16;
	int sum;
	int t1 = ((!x) << 31) >> 31;//x为0时,t1全为1,x不为0时,全为0
	int t2 = ((!~x) << 31) >> 31;//当x为-1时,t2全为1,否则,全为0
	int op = x ^ ((x >> 31));//正数不变,负数取反
	shift16 = (!!(op >> 16)) << 4;//如果高十六位全为0,则0左移4位,不全为0,则1左移4(表示op要右移2^4位)位
	op = op >> shift16;//右移十六位,继续对高十六位进行二分法
	shift8 = (!!(op >> 8)) << 3;
	op = op >> shift8;
	shift4 = (!!(op >> 4)) << 2;
	op = op >> shift4;
	shift2 = (!!(op >> 2)) << 1;
	op = op >> shift2;
	shift1 = (!!(op >> 1));
	op = op >> shift1;
	sum = 2 + shift16 + shift8 + shift4 + shift2 + shift1;
	return(t2 & 1) | ((~t2) & ((t1 & 1) | ((~t1) & sum)));

}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 好的,这里是您要求的回答: 这个问题涉及到计算机系统的实验课程——深入理解计算机系统(CS:APP)的第一个lab任务,也称为Data Lab。这个任务要求学生使用C语言编写函数来实现不同的位级操作,从而深入理解计算机中数字的存储和运算。 ### 回答2: 在Data Lab的第二部分中,我们学习了如何用不超过十六个操作来实现不同的位运算。这些操作包括移位,逻辑运算,比较和掩码等。通过这些操作的实现,我们可以进一步理解底层计算机系统中位运算的实现原理。 在实现这些操作时,我们需要了解底层计算机系统的运算和数据类型。如符号扩展、零扩展和反码等。同时,在编写代码时需要熟练使用位运算的操作符号以及一些基本控制流语句如循环、条件语句等。 除了实现这些基本操作,我们也需要应用这些操作来解决一些实际问题。例如,实现一个函数,将一个十六进制数按位翻转,或是计算一个整数二进制表示中1的数量等。 通过Data Lab的学习,我们深入了解了计算机系统中底层的位运算实现原理,并学会了如何用简洁高效的代码实现这些操作。同时,这些操作也常常被用在各种领域的计算机编程中,对于未来的学习与工作都有很大的帮助。 ### 回答3: 在《深入理解计算机系统》lab1:data lab(二)中,我们主要学习了位运算和两个的补码表示。这些知识对于我们了解计算机的原理和编码方式非常重要。 在这个实验中,我们需要完成一些与位运算相关的任务。其中包括: 1. 实现位运算。我们需要用 C 语言实现一些常见的位运算,如与、或、非、异或、左移、右移等。 2. 计算 x 的相反数。 3. 检查 x 是否为零。 4. 判断 x 的符号是否为负数。 5. 计算 x 和 y 的和,但不能使用加法运算。 为了完成这些任务,我们需要对 C 语言数据类型的细节和位运算的机器级实现有一定的了解。例如,我们要了解有符号整数和无符号整数的区别,以及它们在机器上的表示方式。我们还需要理解位运算的计算过程,包括位移的规则、符号扩展和逻辑运算等。 通过这个实验,我们可以更深入地理解计算机的工作原理,学会用底层的方式实现高级的运算,掌握常用的位运算技巧。这对于提高编程的效率和代码的可读性都有很大帮助。同时,这也为后续的计算机科学学习打下了坚实的基础。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值