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

    xiaoxiao2024-12-19  10

    Data lab的奇妙冒险

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

    我所完成的冒险有:

    bitXorTminisTmaxallOddbitsnegateisAsciiDigitconditionalisLessorEquallogicalNeghowmanybits

    冒险注意事项: 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) 沿着这个思路走下去,只需要通过其符号位来判断:

    x=0::x与-x的符号位都为0。x=OX80000000:x与-x的符号位都为1。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)

    op=x,op>>16为0,则shift16=0,op仍然等于xop>>8!=0,shift8=8,op=op>>8op>>4=0,shift4=0op>>2=0,shift2=0op>>1=0,shift1=0sum=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)