[关闭]
@ysner 2021-10-19T18:34:37.000000Z 字数 3958 阅读 1603

CSAPP dataLAB实验报告

一、实验目的

本实验目的是加强学生对位级运算的理解及熟练使用的能力。

二、报告要求

本报告要求学生把实验中实现的所有函数逐一进行分析说明,写出实现的依据,也就是推理过程,可以是一个简单的数学证明,也可以是代码分析,根据实现中你的想法不同而异。

三、函数分析

1、bitAnd函数

函数要求:

函数名 bitAnd
参数 int x, int y
功能实现 x&y
要求 只能使用 ~ 和 运算符,将结果返回。

实现分析:

由于限制了只能使用~和这两种运算符,所以可能情况数很少,穷举一下表达式就出来了。

函数实现:

  1. int bitAnd(int x, int y) {
  2. return ~(~x|~y);
  3. }

2、getByte函数

函数要求:

函数名 getByte
参数 int x, int n
功能实现 从数字x中提取出右数第n个字节
要求 只能使用 ! ~ & ^ + << >> 运算符,将结果返回。

实现分析:

主要想法是先将右移个字节,再截取出最右的字节。
1个字节对应2个十六进制位,2个十六进制位对应8个二进制位。
所以右移个字节右移个二进制位。
然后再用相与,截取出最右的字节即可。

函数实现:

  1. int getByte(int x, int n) {
  2. return (x>>(n<<3))&255;
  3. }

3、logicalShift函数

函数要求:

函数名 logicalShift
参数 int x, int n
功能实现 用逻辑右移的方式,将数字x右移n个二进制位
要求 只能使用 ! ~ & ^ + << >> 运算符,将结果返回。

实现分析:

右移很简单,但是逻辑右移补的是符号位。。。
所以考虑构造一个数来相与,把补的变成,同时保证其它的位不变。
哪些位不需要变呢?初始状态下的右位肯定都需保证不变,于是构造
然后按题意将其右移位,最后左移位给符号位。
最后把答案和相与,就可以去除补上的符号位啦。

函数实现:

  1. int logicalShift(int x, int n) {
  2. int ans=x>>n;
  3. int t=(~(1<<31)>>n<<1)+1;
  4. return ans&t;
  5. }

4、 bitCount函数

函数要求:

函数名 bitCount
参数 int x
功能实现 统计数字x的二进制形式中1的个数
要求 只能使用 ! ~ & ^ + << >> 运算符,将结果返回。

实现分析:

一开始的构想是每次把右移位,与相与,并将每次与的结果累加起来就是答案。
道理是没错,但是这里限制只能用个运算符,按上面搞法都个了。。。
所以考虑用分治思想来优化,使得一次运算能完成多次累加。
第一步运算,将相邻两个二进制位分为一组。组内让右移位再与相与的结果,和不右移与相与的结果相加,这样每组内得到/存的就是该组内二进制的个数。
第二步运算,将相邻四个二进制位分为一组。组内将下属的两个两位数相加,就可以得到整个组内二进制的个数。具体来说就是把两个两位数移到组内右侧,相加。即让右移位再与相与的结果,和不右移与相与的结果相加。
以此类推。。。
第三步运算,合并了相邻八个二进制位中的个数。
第四步运算,合并了相邻十六个二进制位中的个数。
第五步运算,统计完成。
代码中的是为了消除逻辑右移中符号位带来的影响。

函数实现:

  1. int bitCount(int x) {
  2. int t1=0x55|(0x55<<8);
  3. int t2=0x33|(0x33<<8);
  4. int t3=0x0F|(0x0F<<8);
  5. int t4=0xFF|(0xFF<<16);
  6. int t5=0xFF|(0xFF<<8);
  7. t1=t1|(t1<<16);
  8. t2=t2|(t2<<16);
  9. t3=t3|(t3<<16);
  10. x=(x&t1)+((x>>1)&t1);
  11. x=(x&t2)+((x>>2)&t2);
  12. x=(x&t3)+((x>>4)&t3);
  13. x=(x&t4)+((x>>8)&t4);
  14. x=(x&t5)+((x>>16)&t5);
  15. return x;
  16. }

5、bang函数

函数要求:

函数名 bang
参数 int x
功能实现 !x
要求 只能使用 ~ & ^ + << >> 运算符,将结果返回。

实现分析:

有个独有的特性“取相反数不会改变符号”。
所以当与其相反数相或时,只有当时符号位才为正,否则符号位为负。
但是这里直接判符号位不可行,因为不能用

换个方向,当与其相反数相或时,若右移31位,只有当时值为,否则值一定为。如此再加上就满足题目要求了。

函数实现:

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

6、 tmin函数

函数要求:

函数名 tmin
参数 void
功能实现 找到最小的二进制补码整数
要求 只能使用 ! ~ & ^ + << >> 运算符,将结果返回。

实现分析:

众所周知,二进制补码没有负数的概念。
于是肯定是,即

函数实现:

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

7、 fitsBits函数

函数要求:

函数名 fitsBits
参数 int x, int n
功能实现 判断x是否能表示为n位二进制补码整数
要求 只能使用 ! ~ & ^ + << >> 运算符,将结果返回。

实现分析:

如果能表示为位二进制补码整数,它的范围一定在右以内。
那么将其左移位再右移位时,就没有溢出/数字损失,其大小不会改变。
最后判断左右移后的数是否与原数相等即可,判断方法是相异或。

然后发现题目不提供减号。。。不过根据补码里相反数定义

函数实现:

  1. int fitsBits(int x, int n) {
  2. int t=33+~n;//32-n
  3. int ans=x<<t>>t;
  4. return !(ans^x);//异或可以用来判断是否相等
  5. }

8、divpwr2函数

函数要求:

函数名 divpwr2
参数 int x, int n
功能实现 x/(2^n), 0<=n<=30
要求 只能使用 ! ~ & ^ + << >> 运算符,将结果返回。

实现分析:

问题还是出在逻辑右移补符号位。。。
这里我分情况讨论。
为正数,直接
为负数,就是答案。
换掉减号:,故

函数实现:

  1. int divpwr2(int x, int n) {
  2. int tag=x>>31;
  3. return (x+(((1<<n)+~0)&tag))>>n;
  4. }

9、 negate函数

函数要求:

函数名 negate
参数 int x
功能实现 -x
要求 只能使用 ! ~ & ^ + << >> 运算符,将结果返回。

实现分析:

根据教材里补码相反数定义得

函数实现:

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

10、isPositive函数

函数要求:

函数名 isPositive
参数 int x
功能实现 判断x是否大于0,是则返回1
要求 只能使用 ! ~ & ^ + << >> 运算符,将结果返回。

实现分析:

直接判断大于0不好弄,但是判断和负数还是很简单的。
于是从反面考虑,用判断是否为,用取出符号位来判断是否为负数.
这两个都不是,那就是正数了QAQ。

函数实现:

  1. int isPositive(int x) {
  2. return !(!x|(x>>31));
  3. }

11、isLessOrEqual函数

函数要求:

函数名 isLessOrEqual
参数 int x, int y
功能实现 如果x<=y则返回1,否则返回0
要求 只能使用 ! ~ & ^ + << >> 运算符,将结果返回。

实现分析:

这里的坑在于可能溢出。
毕竟可以搞极大正数减极大负数。
于是分情况讨论。
如果符号位相同,直接减。
如果符号位不同,则变为判断的符号位是否为

还有判断时应该计算而不是,因为是同号且同结果的。

函数实现:

  1. int isLessOrEqual(int x, int y) {
  2. int tt=(y+~x+1>>31)&1;
  3. int tx=(x>>31)&1;
  4. int ty=(y>>31)&1;//小心极端情况
  5. return ((tx^ty)&tx)|(!(tx^ty)&!tt);
  6. }

12、 ilog2函数

函数要求:

函数名 ilog2
参数 int x
功能实现 返回x的以2为底的对数
要求 只能使用 ! ~ & ^ + << >> 运算符,将结果返回。

实现分析:

本题实际上是判断的最高位在哪里。
然后发现一定是正数且符号数最多,可暴力累加。

以上想法过于暴力不可取,我想了一个机智一点的算法:二分查找。
是一个范围基准点
第一次搜索,范围大小,判断是否越过了中位数。如果越过了,就在左半边找,,否则在右半边找。
第二次搜索,范围大小,判断是否越过了中位数。如果越过则去左半边,,否则去右半边。
以此类推。。。
(双感叹号!!可以把一切正数变为1)

函数实现:

  1. int ilog2(int x) {
  2. int t=(!!(x>>16))<<4;
  3. t=t+((!!(x>>8+t))<<3);
  4. t=t+((!!(x>>4+t))<<2);
  5. t=t+((!!(x>>2+t))<<1);
  6. t=t+(!!(x>>1+t));
  7. return t;
  8. }

四、实验总结

本次实验中非独立完成,需予以重点关注。
的问题出在:
不知道如何使得覆盖到符号位。然而后来发现:逻辑右移后高位全变0,符号位就在数据最高位的左边一位。
的问题出在:
没想到这种过于高妙的分治方法。
的问题出在:
没想到,相对于其它数,0有个独有特性“取反不变号”。
的问题出在:
不知道当为负数时该怎么处理,最后发现是书上的结论。
这些问题均通过向巨佬同学请教、上网搜索得以解决。

建议:希望实验文档内容能与bits.c内容统一起来。我倾向于多加几道题,这样更有利于同学们增强代码能力。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注