CS:APP datalab 记录

CS:APP这本书真的可以说是计算机的内容 给你从头讲到尾了,虽然每个领域方面的深度不够,但是已经足够了,因为每一个领域都不是这么简单就能够说完的,这本书能把这么多东西讲得很清楚真的不容易,所以看完建议挑战一下lab。

 

要求:

不允许使用条件语句和循环语句,只允许使用8个运算符:! ˜ & ˆ | + >来完成,某些题目会额外限制运算符数量,最大只能使用8位整数。

 

PART1

bitAnd

只能使用~和|运算符完成&运算,运算符最多只能操作8次。

简单,对两个操作数分别取反再|就是他们两个都没有的位,然后再取反就是他们两个都有的位了。

int bitAnd(int x, int y) {
    return ~((~x)|(~y));
}

 

getByte

获得x的第n个字节,运算符最多操作6次,只能使用!~&^|+<<>>。

也很容易,首先很容易想到获得x的第n个字节的方法是x>>(n*8)&0xff。但是不能用*号,所以改用<<运算符来代替*号。

int getByte(int x, int n) {
    return x>>(n<<3)&0xff;
}

 

logicalShift

实现逻辑右移,只能使用~&^|+<<>>运算符,最大操作数是20。

这题颇有难度,首先我们普通的>>运算是算数右移,在这基础上把前n位清0就可以得到逻辑右移的结果,把前n位清0等于构造32-n位的1与之做&运算,构造这个可以用2**x - 1的方法,那么2**x又等于2<<x,-1则等于0取反。但是构造32-n和无法使用long long类型让我头疼,结果我还是去看了别人怎么写这个。。。用左移再右移的方法防止溢出的同时可以利用符号位来让前面填满1。这是个好技巧。

int logicalShift(int x, int n) {
    return x>>n&(~(1<<31>>n<<1));
}

 

bitCount

计算整数里有多少个位是1,只能使用~&^|+<<>>运算符,最大操作数40。

这题直接算的话是肯定超操作数的,那么只能想办法分块然后同时操作了,我们把每8位分成一块,块里面的每一位我们可以枚举用&操作来判定,最后再把所有块的结果加起来就完了。

int bitCount(int x) {
    int mask = ((1<<8|1)<<8|1)<<8|1;
    int sum = x&mask;
    sum += x>>1&mask;
    sum += x>>2&mask;
    sum += x>>3&mask;
    sum += x>>4&mask;
    sum += x>>5&mask;
    sum += x>>6&mask;
    sum += x>>7&mask;
    sum += sum>>8&0xff;
    sum += sum>>16&0xff;
    sum += sum>>24&0xff;
    return sum&0xff;
}

 

bang

做!运算,只能用~&^|+<<>>,最大操作数12。

这个首先得知道几个技巧,假设x的最低位为1的位置是i,x&(~x)+1可以得到结果只保留第i个1的数,即结果是2的i次方,而x|(~x)+1可以得到i及他以上的位置全部为1的数,即得到...111111100000...(i个0,总位数-i个1)。而0是没有1的,所以0做这两个操作结果还是0。而其他数做上述的第二个操作得到的最高位必定是1,那么我们就可以根据最高位来判定。

int bang(int x) {
    return ((x|((~x)+1))>>31)&1^1;
}

 

PART2

在这个部分可以使用条件和循环语句,可以使用int和unsigned类型的整数,不能使用union,struct,数组,不能使用浮点数,浮点数都以unsigned类型传递并返回。

tmin

返回补码整数最小值。最大操作数4。

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

 

fitsBits

判断是否能用n位二进制表示一个数,可以返回1,否则返回0.最大操作数15,只能使用!~&^|+<<>>。

这题描述不太好,怎么才算能用n位来表示一个数呢,他的意思是前缀相同的话是可以用一位来省略的,比如前缀全是0就可以用一个...0来表示,全是1则用...1来表示,那么问题就转换为去掉后缀n位后是否全是1或者全是0,具体思路看下面代码。

int fitsBits(int x, int n) {
    int tmp = x>>(n+(~0));
    return !tmp|(!(tmp+1));
}

 

divpwr2

计算x/(2**n)。结果向0取整。也就是负数向上取整,正数向下取整。最大操作数15,只能使用!~&^|+<<>>。

若是向下取整,则就是简单的右移运算,即x>>n就可以得到答案,题目需要向0取整,则只要在这基础上进行判定如果x<0&&x%2**n则让结果+1就可以得到答案。x<0直接判符号位,x%2**n则可以用&符号来解决,因为2**n是2的倍数。

int divpwr2(int x, int n) {
    int add_number = (x>>31&1)&(!!(x&(1<<n)+(~0)));
    return x>>n+add_number;
}

 

negate

计算-x。

这题就是送的嘛。。。直接套概念,取反+1完了。

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

 

isPositive

判断x是否>0,是返回1,否则返回0。最大操作数5,只能使用!~&^|+<<>>。

首先如果判定是否>=0则只需要看符号位就好,所以只要在这基础上再判定是否为0就可以了,但是由于操作数只能为5次,这里将问题转换为是否为<=0再取反。

int isPositive(int x) {
    return !(x>>31&1|(!x));
}

 

isLessOrEqual

判断x<=y,是返回1,不是返回0。最大操作数24,只能使用!~&^|+<<>>。

这题比较繁琐,首先你只能用位运算,但是x<=y如果不转换问题的话是最终必须要用逻辑运算符的,故第一个问题得先转换位y-x>=0,上面的题已经处理过-x和>=0的思路了,但是这道题还缺一个点是溢出问题,y-x是可能溢出的,所以我们要先解决溢出问题,分几种情况,如果y>0,x<=0那么我们可以直接得到答案,同理y<=0,x>0也是一样,也就是说我们只需要处理x,y同号的情况做y-x的判定即可。

int isLessOrEqual(int x, int y) {
    int sign_x = x>>31&1;
    int sign_y = y>>31&1;
    int res = y+(~x)+1;
    return (sign_x&!sign_y)|(!(!sign_x&sign_y)&(!(res>>31&1)));
}

 

ilog2

求2为底x的对数,并且向下取整。最大操作数90,只能使用!~&^|+<<>>。

说实话这个90真没多大意义,因为一位位算刚好超出几次,那没法一位位算就只能二分了。。

int ilog2(int x) {
    int res = !x;
    res = res + ((!!(x>>16))<<4);
    res = res + ((!!(x>>res>>8))<<3);
    res = res + ((!!(x>>res>>4))<<2);
    res = res + ((!!(x>>res>>2))<<1);
    res = res + ((!!(x>>res>>1)));
    return res;
}

 

float_neg

求浮点数f的负数-f。当参数是NaN时返回参数,可以使用包含整数或者无符号整数的操作,可以使用||,&&还有if和while。最大操作数10。

这题其实也没啥,翻翻书看看float表示1(符号位)+8(指数位)+23(尾数),所以正常情况下直接把符号位取反就好。

但是还有NaN,NaN表示指数位全1尾数非全0,Inf指数位全1尾数全0,特判一下这两种情况就ok了。

unsigned float_neg(unsigned uf) {
    if ((uf>>23&0xff) == 0xff)
        if (uf&(1<<23)-1)
            return uf;
    return uf^0x80000000; 
}

 

float_i2f

将int值转为float值,允许的操作同上,最大操作数30。

基本也是考定义和几种浮点数特殊表示法,还有向偶数舍入,舍入后如果尾数需要+1溢出直接+的话可以可以得到结果的,因为+1溢出后阶数就会+1,尾数会清0,刚好就是我们想要的结果。

浮点数的几种特殊表示:

1.  0的表示,如果阶码为0,尾数是0,那么根据符号位来表示这个数是+0还是-0。

2.  阶码为0跟阶码为255的情况都是特殊情况,其余都是正常情况,正常情况为规范编码。下面讨论非规范编码。

    阶码全1:NaN或者Inf,情况上面的题目已经说了。

    阶码全0,非规范编码,尾数默认非1.xxx开头,默认为2的-126次方,即可能为0.xxx*2^-126。

unsigned float_i2f(int x) {
    unsigned number1, number2;
    unsigned lefttop = 1<<31;
    unsigned sign = x&lefttop;
    unsigned leftshift = 0;
    unsigned abs = sign?-x:x;
    if(!x) return 0;
    while(abs){
        if(abs&lefttop){
            abs <<= 1;
            leftshift++;
            break;
        }
        abs <<= 1;
        leftshift++;
    }
    number1 = (abs & 0xFFFFFE00) >> 9;
    number2 = abs & 0x000001FF;
    if(number2>0x100)
        number1 += 1;
    else if(0x100 == number2 && number1 & 1)
        number1 += 1;
    return sign|(127+32-leftshift<<23)+number1;
}

 

float_twice

计算2*f,允许的操作同上,最大操作数30。

上题我已经说明的浮点数的所有情况,这题只需要根据情况来判定返回结果就好。送分题。

unsigned float_twice(unsigned uf) {
    unsigned S = uf&0x80000000;
    unsigned E = uf&0x7F800000;
    unsigned M = uf&0x007FFFFF;
    if(0x7F800000==E)
        return uf;
    if(E)
        return S|E+0x00800000|M;
    return S|E|(M<<1);
}

 

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值