CSAPP-datalab

写在前面的话:
这个实验网上已经有很多版本的实现方式了,对于我这个编程菜鸟而言这个实验真的很难,建议大家第一次做的时候先挑软柿子捏,把自己能想出来的先做完后再去想难的,真的想不出来就及时看网上其他人的实现思路。全部做完后隔几天再做一次,相信你能有全新的理解。


1.bitAnd


1.1【题目要求】

/*
bitAnd - 使用 ~ 和 | 实现 x&y
例子:bitAnd(6, 5) = 4
合法操作:~ |
最大操作数:8
难度评级:1
*/

1.2【解题方法】

运用德摩根定理,x&y <==> ((x) | (~y))

1.3 【代码】

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

2.getByte


2.1 【题目要求】

/*
getByte - 从一个字中提取第n个字节
字节从0(LSB)到3(MSB)编号
例子:getByte(0x12345678,1)= 0x56
合法操作:!〜&^ | + << >>
最大操作数:6
难度评级:2
*/

2.2 【解题方法】

先将n扩大8倍,由于不能使用*操作,所以只能用左移3位来代替;
再将x右移n位,这要所要求的那个字节就会来到最后八位,最后再用16进制数0xff与x相与即可;

2.3 【代码】

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

3.logicalShift


3.1 【题目要求】

/*
logicalShift - 使用逻辑右移将 x 向右移动 n 位
可假设 0 <= n <= 31
例子:logicalShift(0x87654321,4) = 0x08765432
合法操作:!〜&^ | + << >>
最大操作数:20
难度评级:3
*/

3.2 【解题方法】

逻辑右移是不考虑所输入的数的符号位的,高位自动补0,在这个程序的开头的提示中说,假设所有的右移都为算数右移“2. Performs right shifts arithmetically.”,所以直接使用右移操作符>>会导致结果出错;所以就要对结果进行修正。修正的目的在于将右移后的高n为全部变为零,即我们要构造一个高n位为0低32-n位为1的数与算数右移n位后的x相与再返回最终结果;

3.3 代码

错误示例:

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

在一开始我直接想当然的采取上述方法进行操作,发现结果是错误的,原因在于y再右移的过程中虽然也是移了n位,但是由于本身其第31位就存在一个1,导致右移n位后变成了高n+1位都为1;
例如假设右移三位,则y一开始为0x10000000,右移三位后变为0xf0000000,这就变成了高四位为1,而我们只想要高3位为1,这个时候就要对于y做进一步修正,使其左移1位;

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

而且我发现这个程序很矫情的一个点是,你所有的变量必须在一开始就声明,例如上面函数中的y,如果你不一开始就声明出来他,编译的时候就会报错:
例如:

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

4.bitCount


4.1 【题目要求】

/*
bitCount - 返回 word 中 1 的个数
例如:bitCount(5) = 2,bitCount(7) = 3
合法操作:! ~ & ^ | + << >>
最大操作数:40
难度等级:4
*/‘

4.2 【解决方案+代码】

int bitCount(int x) {
  int c,t1,t2,t3,t4,t5;
  t1=0x55|((0x55)<<8);
  t1=t1|(t1<<16);
  t2=0x33|((0x33)<<8);
  t2=t2|(t2<<16);
  t3=0x0f|((0x0f)<<8);
  t3=t3|(t3<<16);
  t4=0xff|((0xff)<<16);
  t5=0xff|((0xff)<<8);
  c=(x & t1)+(x>>1 & t1);
  c=(c & t2)+(c>>2 & t2);
  c=(c & t3)+(c>>4 & t3);
  c=(c & t4)+(c>>8 & t4);
  c=(c & t5)+(c>>16 & t5);
  return c;
 }
t1=0x55555555
t2=0x33333333
t3=0x0F0F0F0F
t4=0x00FF00FF
t5=0x0000FFFF

因为该实验限制了无法直接使用过大的整形数据,所以只能通过不断地移位来得到我们想要的整型数据
这个函数用来计算一个32位整数中二进制位为1的个数(也称作"汉明重量")。
要计算一个32位二进制数有多少个1,一个直接的思路就是将他的各个位相加得到的最终结果就是我们要的答案。可是如何在只能使用位运算的条件下实现这个目的呢?函数使用了一种被称为"分治算法"的思想:
首先将 32 位分为 16 组,每组为连续 2 位,令每组前后 1 位相加,得到的值存储在该 2 位中
再将 32 位分为 8 组,每组为连续 4 位,将 4 位的前后 2 位相加,得到的值存储在该 4 位中
依次类推,最终得到 32 位相加的值
n 位长度的数只需要相加 logn 次即可
还可以参考的网站:c - How to implement Bitcount using only Bitwise operators? - Stack Overflow


5.bang


5.1 【题目要求】

/*
bang - 计算 !x ,不能使用 !
示例: bang(3) = 0, bang(0) = 1
允许使用的操作符: ~ & ^ | + << >>
最大操作数: 12
难度评级: 4
*/

5.2 【解决方案】

本题要求在不使用!符号的情况下实现逻辑非运算,考虑一个数与他的相反数的逻辑或运算,这个运算除了0之外,或运算的结果的最高位都是1,将其右移31位之后变为0xffffffff,只有当x=0时,右移结果才为0x00000000。

5.3 【代码】

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

6.tmin


6.1 【题目要求】

/*
tmin - 返回最小的二进制补码整数
合法操作:!〜&^ | + << >>
最大操作数:4
难度评级:1
*/

6.2 【解题方法】

没啥好说的,最小的二进制补码整数,根据补码运算规则,即第一位是1后续全部都是0的二进制串为最小

6.3 【代码】

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


7.fitsBits


7.1 【题目要求】

/*
fitsBits - 如果x可以表示为n位的二进制补码整数,则返回1。
1 <= n <= 32
例如:fitsBits(5,3) = 0,fitsBits(-4,3) = 1
允许使用的运算符: ! ~ & ^ | + << >>
最大操作数:15
评分:2
*/

7.2.1【解题方法1】

我们知道,对于一个n位的二进制补码数,最高位为符号位,表示正负号,其余n-1位表示数字的大小。因此,一个n位的二进制补码数最大值为(2^(n-1)-1), 最小值为-2^(n-1)。如果给定的整数x可以用n位的二进制补码表示,则它的值应该在这个范围内。
对于这个程序而言,一切操作都建立在32位基础上,所以我们先要对给出的x做一次符号位扩展:我们需要将x的二进制表示左移32-n位(即最高位移动到符号位),然后将高位的1或0进行截断。如果x可以用n位的二进制补码表示,则这个操作后得到的值应该等于x本身;否则,这个操作后得到的值与x不相等。

7.3.1代码

int fitsBits(int x, int n) {
  int shift=32+((~n)+1);
  return !(((x<<shift)>>shift)^x);
}

7.2.2【解题方法2】

考察一个x能否用n位二进制补码表示,就代表该二进制的最高位应当为该数的符号位。所以先将这个数向右移n-1位,如果移动完的结果不是0或者-1,那么该数就不能用n位二进制表示

7.3.2代码

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


8.divpwr2


8.1 【题目要求】

/*
divpwr2 - 计算 x/(2^n),其中 0 <= n <= 30
向零舍入(意味着正数可以正常的向下舍,这也就意味着可以直接算数右移;对于负数而言要向上舍入)
示例:divpwr2(15,1) = 7,divpwr2(-33,4) = -2
允许操作:! ~ & ^ | + << >>
最大操作数:15
评分:2
*/

8.2 【解题方法】

该的题目要求中说要向0舍入,则意味着正数可以正常的算数右移向下取整,但是对于负数来说,就要向上取整,利用上课时老师所交的向上取整方法:“先涨价,再降价”。先加上一个2的n次方-1,再进行算数右移,返回最终答案即可。

8.3 【代码】

int divpwr2(int x, int n) {
   int sign=x>>31;
   int tmp=sign & ((1<<n)+(~0));//其中~0就是-1(自己好好思考一下)
   return (x+tmp)>>n;
}

9.negate


9.1 【题目要求】

/*
negate - 返回-x
例如:negate(1) = -1。
允许使用的运算符: ! ~ & ^ | + << >>
最大操作数:5
评分:2
*/

9.2 【解题方式】

本题看似是求负数,实际上仔细分析之后可以看出,其实本质上就是求已知一个数求他相反数的补码。那么我们一共有三种求法:
①各位取反+1
②找第一个“1”,找到之后他接下来的每一位取反
③2的n次方-|x|,n为x补码位数,减出来的结果化为二进制表示
显然对于这道题的运算符限制,我们对于①的可执行性最高,所以采用①的方法来解决这道题目

9.3 【代码】

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


10.isPositive


10.1 【题目要求】

/*
isPositive - 如果x > 0返回1,否则返回0
例子: isPositive(-1) = 0.
允许使用的运算符: ! ~ & ^ | + << >>
最大操作数: 8
难度评级: 3
*/

10.2 【解题方法】

先分析什么时候会返回0,什么时候会返回1,根据题目要求,只有在x是负数或者x等于0时才会返回0;
这题一开始想到的就是截断最高位的符号位直接返回:

int isPositive(int x) {
    return  !(x & 0x80000000);
}

但这样做存在一个问题,这样的话0返回值也会是1。所以我们要加一步对于0的特判。

10.3 【代码】

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

11.isLessOrEqual


11.1 【题目要求】

/*
isLessOrEqual - 如果x <= y,则返回1,否则返回0
例子: isLessOrEqual(4,5) = 1.
允许使用的运算符: ! ~ & ^ | + << >>
最大操作数: 24
难度评级: 3
*/

11.2 【解题方式】

判断谁大谁小的方式就是两个数做一次减法运算,这题就是用y-x来判断y与x的大小关系,但要注意的是可能会有溢出的情况所以不能完全依赖相减结果的符号来做出判断。

11.3 【代码】

int isLessOrEqual(int x, int y) {
  int signy=(y>>31)&1;
  int sameSign=!((x^y)>>31);
  int subSign=!((y+(~x)+1)>>31);
  return (subSign & sameSign) | ((!sameSign) & (!signy));
}

12.ilog2


12.1 【题目要求】

/*
ilog2 - 返回x的以2为底的对数的下限,其中x > 0
例子: ilog2(16) = 4
允许使用的运算符: ! ~ & ^ | + << >>
最大操作数: 90
难度评级: 4
*/

12.2 【解决方法】

找到最高位的1在哪一位,采用二分法,将原二进制串不断地分为左右两份,高位部分有1则下一次在高位半边操作,反之则在低位半边操作;这里注意的是,先判断高位半边是有利的,因为那一次的1的幂次一定要大于低位半边的次幂。

12.3 【代码】

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

注意最外层的红色括号一定要加,因为+的优先级大于<<


13.float_neg


13.1 【题目要求】

/*
float_neg - 返回浮点参数 f 的等效二进制表示的负数。
函数的输入和输出都以无符号整数的形式传递,但它们应该被解释为单精度浮点数的二进制表示。
当参数为 NaN 时,返回参数。
允许使用的操作:任何整数/无符号操作,包括 ||、&&。还可以使用 if 和 while。
最大操作数:10
评分:2
*/

13.2 【解题方法】

if(uf>0x7f800000) return uf;
   return uf^(0x80000000);

想投机取巧这样做是不可以的,因为f有可能是负数
我们要考虑的是NaN的特点是什么,无非就是阶码全为1,尾数不为全0

13.3 【代码】

unsigned float_neg(unsigned uf) {
    int isNaN=(((uf>>23)&0xFF)==0xFF)&& (uf<<9);
    return isNaN ? uf : (uf^(1<<31));
}

14.float_i2f


14.1 【题目要求】

/*
float_i2f - 返回表达式 (float) x 的位级等效值
返回结果作为无符号整数,但是它应该被解释为单精度浮点数的位级表示。
合法操作: 任何整数/无符号操作,包括 ||,&&,if,while
最大操作数: 30
评级: 4
*/

14.2 【解题方法】

这段代码实现了将一个整数转换为单精度浮点数的功能。

具体实现步骤如下:

  1. 判断输入的整数是否为 0。如果是 0,直接返回 0。
  2. 如果输入的整数 x 是负数,记录符号位,并将 x 取绝对值。记录符号位的方法是将一个 32 位的二进制数的最高位(即第 31 位)设置为 1。
  3. 对绝对值 absx 进行位运算,计算出 absx 左移的位数 shiftle,以及左移后得到的结果 afshift。
  4. 根据 afshift 的低 9 位,判断需要进行四舍五入的情况。如果需要四舍五入,设置 flag 为 1,否则为 0。
  5. 将单精度浮点数的各个部分拼接在一起。符号位使用 step 2 中记录的 sign,尾数部分使用 afshift 右移 9 位得到的结果,指数部分使用 159 减去 shiftle 得到的结果,四舍五入部分使用 step 4 中计算的 flag。
  6. 将步骤 5 得到的各个部分拼接在一起,返回结果。注意,在进行拼接时,需要按照浮点数的格式来排列各个部分的顺序。(先加低位再加高位)

14.3 【代码】

  unsigned shiftle,afshift,temp,absx,sign,flag;
  absx=x;
  shiftle=0;
  sign=0;
  if(x==0) return 0;
  if(x<0) {
  sign=0x80000000;
  absx=-x;
  }
  afshift=absx;
  while(1){
  temp=afshift;
  afshift<<=1;
  shiftle++;
  if(temp & 0x80000000) break;//first 1;//注意看这里是temp在按位与,
  //实际上这一条为真相当于多左移了一位,这样在后续计算就没问题了,
  //一开始想了好久觉得这个代码有点问题但后来自己敲的时候才发现这里原来是我没看到。
  }
  if((afshift & 0x01ff)> 0x0100) flag=1;
  else if ((afshift & 0x03ff)== 0x0300) flag=1;//向偶数舍入
  else flag=0;
  return sign+(afshift>>9)+((159-shiftle)<<23)+flag;//127+32-shiftle=159-shiftle
}


15.float_twice


15.1 【题目要求】

/*
float_twice - 返回表达式 2*f 的位级等效值,其中 f 是单精度浮点数参数。
参数和结果都作为无符号整数传递,但是它们应该被解释为单精度浮点数的位级表示。
当参数是 NaN 时,返回参数本身。
合法操作:任何整数/无符号操作,包括 ||,&&,if,while。
最大操作数:30
评级:4
*/

15.2 【解题方法】

主要就是考虑阶码exp的取值情况
如果exp全1,则直接返回uf;
如果exp全0,那么直接尾数frac左移一位即可;
如果exp非全1或全0,那么就exp+1。

15.3 【代码】

unsigned float_twice(unsigned uf) {
  if((uf & 0x7f800000)==0x7f800000) return uf;
  else if((uf & 0x7f800000)==0) return ((uf & 0x007fffff)<<1)|(uf & 0x80000000);
  else return uf+0x00800000;
}
  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值