CSAPP实验记录(1)--------- DataLab

本文详细解析了DataLab实验中的十道位运算题目,涉及异或、最小补码、最大值检测、二进制位操作、逻辑非、浮点数运算和位数表示等,通过实例展示了如何利用受限的运算符完成高级逻辑。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Datalab

Datalab实验是关于数据的机器级表示,实验要求实现给定的位级运算符,同时要满足一些要求,如只能使用某些限定的运算符,运算符总数不超过某数字等。第一次刷感觉难度还是很大的。

题目一:bitXor

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

第一题,要求利用 ~ 和 & 实现按位异或。前面几道题还是比较简单的。
思路:比较被熟知的异或表达式是这样的 :Xor(x,y)=(!x&y)∣(x&!y) Xor(x, y) = (!x \& y) | (x \& !y) Xor(x,y)=(!x&y)(x&!y)
但是题目要求不能用按位与,所以考虑对这个式子取反:将或逻辑变成与逻辑
!Xor(x,y)=!(!x&y)&!(x&!y) !Xor(x, y) = !(!x \& y) \& !(x \& !y) !Xor(x,y)=!(!x&y)&!(x&!y)
将这个式子再取反一次就可以得到Xor了

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

题目二:tmin

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

求补码int的最小值,很简单。

题目三:isTmax

/*
* isTmax - returns 1 if x is the maximum, two's complement number,
*     and 0 otherwise 
*   Legal ops: ! ~ & ^ | +
*   Max ops: 10
*   Rating: 1
*/
int isTmax(int x) {
   //return 2;
   return !(~(x + x + 0x1)) & !(!(x + 0x1));
}

如果 x 是补码int能表示的最大值,返回1,否则返回0.
思路:int 补码能表示的最大值是 0x7fffffff0x7fffffff0x7fffffff,可以利用其特征使它向零转换。这个数字左移一位再加一之后是全一的位模式(0xffffffff0xffffffff0xffffffff),然后按位取反判断是否为0即可。还要注意 x=0xffffffffx = 0xffffffffx=0xffffffff,也满足同样的特征,所以要排除这种情况。

题目四:allOddBits

/* 
* allOddBits - return 1 if all odd-numbered bits in word set to 1
*   where bits are numbered from 0 (least significant) to 31 (most significant)
*   Examples allOddBits(0xFFFFFFFD) = 0, allOddBits(0xAAAAAAAA) = 1
*   Legal ops: ! ~ & ^ | + << >>
*   Max ops: 12
*   Rating: 2
*/
int allOddBits(int x) {
   //return 2;
   int mask = 0xaa;
   mask = (mask << 8) + mask;
   mask = (mask << 16) + mask;
   return !((x & mask) ^ mask);
}

要求:判断一个整数是否满足所有二进制权值为奇数的位都是1。
思路:要考察某些特定的位,容易想到可以构造这些位的掩码,来吸取 x 的这些位。将吸取结果与0xaaaaaaaa0xaaaaaaaa0xaaaaaaaa异或,判断结果是否为0即可。
还要注意本实验要求代码中不能出现大于 0xff0xff0xff 的立即数,所以需要用 0xaa0xaa0xaa 不断左移来构造掩码。

题目五:negate

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

给定 x ,返回相反数。计算机中整数取相反数就是求补码,这道题属于常识。

题目六:isAsciiDigit

//3
/* 
* isAsciiDigit - return 1 if 0x30 <= x <= 0x39 (ASCII codes for characters '0' to '9')
*   Example: isAsciiDigit(0x35) = 1.
*            isAsciiDigit(0x3a) = 0.
*            isAsciiDigit(0x05) = 0.
*   Legal ops: ! ~ & ^ | + << >>
*   Max ops: 15
*   Rating: 3
*/
int isAsciiDigit(int x) {
   int t1 = !(((~(0x07) & x) >> 3) ^ 0x06);
   int t2 = !(((~(0x01) & x) >> 1) ^ 0x1c);
   return t1 | t2;
}

要求:判断x是否是ASCII字符 ‘0’~‘9’,即是否 0x30<=x<=0x390x30 <= x <= 0x390x30<=x<=0x39
这道题还是有必要仔细记录一下的,因为后面还有一道题叫“isLessOrEqual” ,要求用位运算实现判断 x <= y,起初我想用一个思路解决这两道题,即将x<=yx<=yx<=y转化成x−y<=0x-y<=0xy<=0再进行补码运算后判断正负号。没想到这个方法用在这两道题上都出了问题。
这道题正确的思路:
将0x30与0x39的二进制形式写出来

0x30 = 0011 0000
0x39 = 0011 1001

可以看出来满足0x30<=x<=0x390x30<=x<=0x390x30<=x<=0x39的x可以划分成两种情况:

0011 0xxx           //x代表任意二进制数字
0011 100x           //总共 2^3 + 2 = 10个数字,正好0x30~0x39也是十个数字

然后就可以分两种情况分别判断了,(代码中的t1和t2)。

题目七:conditional

/* 
* conditional - same as x ? y : z 
*   Example: conditional(2,4,5) = 4
*   Legal ops: ! ~ & ^ | + << >>
*   Max ops: 16
*   Rating: 3
*/
int conditional(int x, int y, int z) {
   //return 2;
   int xx = ((!x) << 31) >> 31;
   return (~xx & y) | (xx & z);
}

题目要求用位运算实现三目运算符 ‘???’,根据三目运算符的逻辑(x ? y : z)来看,可以知道它是类似于做一个二路选择,即当x为真时,选择(信号)y作为输出,否则选择(信号)z作为输出,因此本题可以参考数字逻辑中的二路选择器。将x变成全0或全1的掩码,吸取y和z的所有位。
情况一:当x = 0时,掩码应该是全1的位模式。
情况二:当x != 0时,掩码应该是全0的位模式。
代码中的xx,即用x构造的掩码。

题目八:isLessOrEqual

/* 
* 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 a = (x >> 31) & 0x1;             //x的符号
   int b = (y >> 31) & 0x1;             //y的符号
   int c1 = (a & ~b);                   // x<0且y>0的情况
   int c2 = (~a & b);                   // x>0且y<0的情况
   int e = y + (~x + 0x1);              //x-y
   int flag = e >> 31;
   return c1 | (!c2 & !flag);           //如果flag 和 c2 不同则说明了溢出了
}

判断两个数字x,y是否满足 x<=y,满足则输出1.
起初我想将x<=yx<=yx<=y转化成x−y<=0x-y<=0xy<=0再进行补码运算后判断正负号。但是评测时没有得分,查看数据之后才发现我没有意识到x-y存在溢出问题,如果x-y超过int表示范围,判断结果就会出错。所以这道题需要另外的思路。
如果x和y的符号不同,那么正数一定是大于负数的。符号相同则做减法不会溢出,此时可以看差值的符号,详细见代码注释。

题目九:logicalNeg

/* 
* logicalNeg - implement the ! operator, using all of 
*              the legal operators except !
*   Examples: logicalNeg(3) = 0, logicalNeg(0) = 1
*   Legal ops: ~ & ^ | + << >>
*   Max ops: 12
*   Rating: 4 
*/
int logicalNeg(int x) {
   return ((x | (~x + 0x1)) >> 31) + 0x1;
}

题目:不使用 ’ ! ‘,实现逻辑非,即x = 0输出1,x为非0,输出0.
思路:容易想到如果位模式为全0,那么将其加上0x1后就得到true的逻辑,而全1的位模式,加上0x1后就变成全0,也就是false的逻辑。
那么我们可以定义一组操作,使得当x = 0时,经过操作后结果仍为0,之后加上0x1,就变成了true;当x != 0时,经过这组操作变成全1的位模式,我们要找到这样的操作。
对于补码运算,我们知道除了0(其实还有1000…0,但是这种情况没有例外)的补码是自己本身,其他数字的补码都是其相反数,符号位改变。那么如果用原数x与(~x + 0x1)进行按位或,就可以得到符号位为1,对其进行算数右移即可得到全1的位模式,而0的补码是其本身,经过这套操作,结果还是全0的位模式。
(10000.0的补码也是其本身,但因为其本身符号位就是1,所以 x | (~x + 0x1))后也是全1的位模式,所以这种情况不是例外)。

题目十:howManyBits

/* howManyBits - return the minimum number of bits required to represent x in
*             two's complement
*  Examples: howManyBits(12) = 5
*            howManyBits(298) = 10
*            howManyBits(-5) = 4
*            howManyBits(0)  = 1
*            howManyBits(-1) = 1
*            howManyBits(0x80000000) = 32
*  Legal ops: ! ~ & ^ | + << >>
*  Max ops: 90
*  Rating: 4
*/
int howManyBits(int x) {
   int b16,b8,b4,b2,b1,b0;
   int sign=x>>31;
   x = (sign&~x)|(~sign&x);       //如果x为正则不变,否则按位取反(这样好找最高位为1的,原来是最高位为0的,这样也将符号位去掉了)

   // 不断缩小范围
   b16 = !!(x>>16)<<4;           //高十六位是否有1
   x = x>>b16;                   //如果有(至少需要16位),则将原数右移16位
   b8 = !!(x>>8)<<3;             //剩余位高8位是否有1
   x = x>>b8;                    //如果有(至少需要16+8=24位),则右移8位
   b4 = !!(x>>4)<<2;             //同理
   x = x>>b4;
   b2 = !!(x>>2)<<1;
   x = x>>b2;
   b1 = !!(x>>1);
   x = x>>b1;
   b0 = x;
   return b16+b8+b4+b2+b1+b0+1;  //+1表示加上符号位
}

题意:对于整数x,输出用补码表示x,最少需要多少二进制位。
这道题应该是DataLab里最难的一道题了,我一开始是几乎没有思路的,容易知道其实这道题就是求x的二进制表示中,最高位的1在第几位,同时对于负数考虑符号位,但之后就不会做了。学习了别人的思路之后才明白了一点。类似二分查找。具体见代码。

第二部分是浮点运算,这部分放宽了对程序的限制,现在可以使用逻辑运算符’&&‘,’||',以及if语句和while语句了。

题目十一:floatScale2

/* 
/* 
* floatScale2 - 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 floatScale2(unsigned uf) {
   int exp = (uf & 0x7f800000) >> 23;
   int sign = uf & (1<<31);
   if(exp==0)
       return uf<<1 | sign;
   if(exp==255)
       return uf;
   exp = exp + 0x1;
   if(exp==255)
       return 0x7f800000 | sign;
   return (exp << 23) | (uf & 0x807fffff);
}

要求:输出浮点数2.0*x。其中返回值都以unsigned的形式给出,但它们都会被解释称float的位模式。
思路:如果不考虑0,溢出和NAN的情况,实际上只需要把原浮点数的阶码+1即可。

题目十二:floatFloat2Int

/* 
 * floatFloat2Int - Return bit-level equivalent of expression (int) f
 *   for floating point argument f.
 *   Argument is passed as unsigned int, but
 *   it is to be interpreted as the bit-level representation of a
 *   single-precision floating point value.
 *   Anything out of range (including NaN and infinity) should return
 *   0x80000000u.
 *   Legal ops: Any integer/unsigned operations incl. ||, &&. also if, while
 *   Max ops: 30
 *   Rating: 4
 */
int floatFloat2Int(unsigned uf) {
    int exp = ((uf & 0x7f800000)>>23) + (~0x7f + 0x1);
    int M = uf & ((1 << 23) + (~0x1 + 1));
    int sign = uf & (1<<31);

    int flag = 1<<22, te = exp;
    int res = 0;

    if((exp) > 30)
    {
        return 0x80000000u;
    }
    else if(exp < 0)
    {
        return 0x0;
    }
    while(flag && te)
    {
        if(M & flag)
        {
            res = res + (1 << (te-1));
        }
        flag = flag >> 1;
        te = te + (~0x1 + 1);
    }
    res = res + (1<<exp);
    if(sign)
    {
        res = ~res + 0x1;
    }
    return res;
}

题意:对于浮点数 f 的位模式(用unsigned类型给出),返回(int)f的值。
这道题倒不是很难,就是有点复杂。首先需要依据阶码,判断0的情况以及INF的情况;随后就是将unsigned类型给出的位模式,化成1.xxxxxx…x ×\times× 2exp2^{exp}2exp的形式,写一个类似二进制数转十进制的程序。我自己最初就像平时写进制转换程序那样写了一个while循环,但是在看了别人的思路后发现其实不用循环,因为数字已经是用位模式表示出来的了,用移位就可以实现转换。

题目十三:floatPower2

/* 
 * floatPower2 - Return bit-level equivalent of the expression 2.0^x
 *   (2.0 raised to the power x) for any 32-bit integer x.
 *
 *   The unsigned value that is returned should have the identical bit
 *   representation as the single-precision floating-point number 2.0^x.
 *   If the result is too small to be represented as a denorm, return
 *   0. If too large, return +INF.
 * 
 *   Legal ops: Any integer/unsigned operations incl. ||, &&. Also if, while 
 *   Max ops: 30 
 *   Rating: 4
 */
unsigned floatPower2(int x) {
    if(x < -126)
    {
        return 0;
    }
    else if(x > 127)
    {
        return (0x7f << 24) + (0x1 << 23);
    }
    return (x + 0x7f) << 23;
}

没想到最后一道题反而比较简单,这道题要求输出(2.0)x(2.0)^x(2.0)x。当无法表示时按情况输出000+INF+INF+INF。根据IEEE754IEEE754IEEE754标准很容易得到答案。所有2x2^x2x的数字都可以表示为
1.00......0×2exp 1.00......0\times2^{exp} 1.00......0×2exp
即位模式
0 xxxxxxxx 00000000000000000000000 0\ xxxxxxxx\ 00000000000000000000000 0 xxxxxxxx 00000000000000000000000
将x加上偏移量后,移位到阶码处即可。

实验结果

断断续续做了几天,终于把DataLab做完了。贴一个DataLab满分截图。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值