CSAPP LAB1 datalab-handout

实验题目:

Datalab-handout

实验目的:

只使用有限数量、规定的操作符条件下,填写bits.c文件中尚未完成的各个函数的内容。并利用dlc和btest两个工具来检测所写的函数代码格式及功能是否正确。

实验环境:

Ubuntu12.04

实验内容及操作步骤:

1、补充完成bits.c中各个函数的内容,并在dlc中验证。

1-1bitAnd这个函数是将两个数x,y进行与运算。根据以前学的离散数学知识,利用德摩根律的,~(x&y)=(~x)|(~y),可得结果。

int bitAnd(int x, int y) {

  x=(~x)|(~y);

  return ~x;

}

1-2、getByte:这个函数是要得到X的第n个字节.则我们需要把留的字节移到最右端,再与0xff相与。0xff代表八位里面后两位是f,最后一句话是为了保证在范围内。

int getByte(int x, int n) {

  x=x>>(n<<3);

  return x&0xff;

}

1-3、logicalShift:实现逻辑右移。我们需要先求出一个左端有n个0,右端是32-n个1的常数,得到这个常数之后就可以和x进行算术右移后的结果相与,这样左端的n位就=0,满足逻辑右移。

int logicalShift(int x, int n) {

  int y=((~0)<<(32+~n)<<1);

  return (x>>n)&(~y);

}

1-4、bitCount:求出32位整数中1的个数。

本题使用了分治思想,对于一个二进制数,要对其中为1的位做计数, 对于1位二进制数来说,1的个数无非就是其本身所表示的1或0。利用这个特性,我们可以先将一个二进制数每一位独立分开为相间隔的两部分, 其每位表示的就是自身的二进制个数,再将两串二进制数对其相加,所得到的每两位分隔的二进制数就是表达这个位置的位为1的个数。

  • ☆本题采用了掩码:0x55555555 \ 0x33333333 \ 0x0f0f0f0f \ 0x0000ffff

int bitCount(int x) {

  int tmp, l1, l2, l4, l8, l16; //tmp is used to save ops

  tmp = (0x55 << 8) + 0x55;

  l1 = (tmp << 16) + tmp; //0x55555555

  tmp = (0x33 << 8) + 0x33;

  l2 = (tmp << 16) + tmp; //0x33333333

  tmp = (0x0f << 8) + 0x0f;

  l4 = (tmp << 16) + tmp; //0x0f0f0f0f

  l8 = (0xff << 16) + 0xff; //0x00ff00ff

  l16 = (0xff << 8) + 0xff; //0x0000ffff

  x = (x & l1) + ((x >> 1) & l1);

  x = (x & l2) + ((x >> 2) & l2);

  x = (x & l4) + ((x >> 4) & l4);

  x = (x & l8) + ((x >> 8) & l8);

  x = (x & l16) + ((x >> 16) & l16);

  return x;

}

1-5bang不用操作符!,来实现!的功能。!x 当且仅当x为0时其为1,其余时候都为0,可以用来区分零和非零数。

非运算表现为,如果x不为 0,则结果为 1,否则结果为 0。也就是说,只要有任何一个二进制位为 1,取非得到的结果就是 0,那么我们就可以将x的所有位 “压缩”到第 0 位。如果x中含有 1,那么第0位就会是 1。

最后一个语句是将 xx 按位取反,然后只留最后一位。这样返回的就是取非的结果了。

int bang(int x)

{

  x = (x >> 16) | x;

  x = (x >> 8) | x;

  x = (x >> 4) | x;

  x = (x >> 2) | x;

  x = (x >> 1) | x;

  return ~x & 0x1;

}

1-6、tmin:返回最小的二进制补码。

int型能表示的最大正数时2^31-1,而2^31可以故意让其上溢出,就可以得到补码的最小值了。

int tmin(void) {

  return 1<<31;

}

1-7、fitBits:x是否可以用n位二进制整数表示,能则返回1,否则返回0。

x是肯定能用32位表示的,能否用n位表示可看成符号拓展的逆过程。所以经过x<<(32-n)>>(32-n)后若与原来的x相等则表示能用n位二进制补码表示x。判断相等可利用异或,相同为0,不同为1,前面加个!得到正确映射关系。

int fitsBits(int x, int n) {

  int shiftnum=32+~n+1;

  int y=x<<shiftnum>>shiftnum;

  return !(y^x);

}

1-8、divpwr2:计算x/(2^n)。

右移运算会向下舍入,而题目要求负数向上舍入。CSAPP 书中的解决方法是为负数添加一个 bias(偏置)值 2^k1,这样右移的时候就可以向上舍入了。x>>31正是为了判断正负,提取了符号位。

int divpwr2(int x, int n)

{

  int bias = (x >> 31) & ((1 << n) + ~0);

  return (x + bias) >> n;

}

1-9、negate:取反,求补码即可。

int negate(int x) {

  return ~x+1;

}

1-10、isPositive:判断x正负。(判断符号位即可)
本来只需要将符号位移到最低位和0x1相与就得到了,但是0这个特殊情况就需要加上后面的~(!x)+1。如果x=0,后面这段结果=1,返回1,如果不等于0,!x=0,0再取反加1=0,正好返回符号位

int isPositive(int x) {

  return !((x>>31)&0x1)+~(!x)+1;

}

1-11isLessOrEqual:判断x<=y是否成立。

分成三种情况,1.x是负数,y是正数,只需要看两个的符号位;2.x是正数,y是负数,返回0;3.x,y同号,算出x-y,看结果的符号位,符号位=1并且两个不相等并且不符合情况2。

int isLessOrEqual(int x, int y) {

  int sx=(x>>31)&0x1;//x符号位

  int sy=(y>>31)&0x1;//y符号位

  int sub=x+~y+1;//x-y

  int sub1=(sub>>31)&0x1;//x-y的符号位

  int yh=sx^sy;//异或

  return (yh&sx)|(sub1&!!sub&!(sx^sy))|!sub;

}

1-12ilog2:求以2为底的对数,log2x。

找到最高位的 1 是第几位减一,但直接逐位右移再判断并不是可行的方法,因为较低位也可能为 0,可能统计不到真正的最高位就以为是最高位了。所以,我们先把非最高位全都设成 1,然后再统计 1 的个数即可。

先是类似于第 5 题 “Bang” 的做法,将高位 “折” 下来。与第五题不同,这里移动的位数从低到高,因为先移 16 位的话,并不能保证高 16 位中能够正确地设 1;相反的,第 5 题使用这种顺序则是可以的。

然后是第 4 题 “bitCount” 的做法,统计有多少个 1,就可以得知最高位的 1 是第几位(从 1 开始计)。不过,由于第 1 位代表 2^0,最后还要将结果减一。

总的来说,就是先把后面的0改1,再数出所有1的个数,最后再-1。                                                                                  

int ilog2(int x)

{

  int mask;

  x = (x >> 1) | x;

  x = (x >> 2) | x;

  x = (x >> 4) | x;

  x = (x >> 8) | x;

  x = (x >> 16) | x;

  mask = (0x55) | ((0x55) << 8);     // 0x00005555

  mask = mask | (mask << 16);        // 0x55555555

  x = (x & mask) + (x >> 1 & mask);  // 2-bit sum

  mask = (0x33) | ((0x33) << 8);     // 0x00003333

  mask = mask | (mask << 16);        // 0x33333333

  x = (x & mask) + (x >> 2 & mask);  // 4-bit sum

  mask = (0x0f) | (0x0f << 8);       // 0x00000f0f

  mask = mask | (mask << 16);        // 0x0f0f0f0f

  x = (x & mask) + (x >> 4 & mask);  // 8-bit sum

  mask = (0xff) | (0xff << 16);      // 0x00ff00ff

  x = (x & mask) + (x >> 8 & mask);  // 16-bit sum

  mask = (0xff) | (0xff << 8);       // 0x0000ffff

  x = (x & mask) + (x >> 16 & mask); // 32-bit sum

  return x + ~0;

}

1-13float_neg:返回参数的相反数。如果参数是INF,则返回参数本身。

参数格式虽为unsigned型,但实为浮点单精度表示,返回-uf。
根据浮点数的表示规则,先通过移位求出exp和frac,根据这两个判断是否是特殊的无穷或者无法表示的数,这种情况就直接返回参数uf,否则就通过和0x80000000异或,符号位取反,其他位不变。

unsigned float_neg(unsigned uf) {

  if(((uf>>23)&0xff)==0xff&&!!(uf&0x7fffff))

  {return uf;}

 return uf^0x80000000;

}

1-14、float_i2f:将一个int型的数转成float单精度表示并返回。

先设置符号位,通过一个循环右移,当移后结果=1,说明数的左边只有一个1,此时就能求得阶码E。考虑特殊情况+0直接返回0,0x80000000返回0xcf000000。

接下来就求一般情况,因为传进来的参数是一个整数,所以不用考虑非规格化数,直接exp=127+E,求frac时,根据阶码E大小,如果<=23需要x左移23-E位,如果>23,就有一些尾数需要舍掉,向右移E-23,再和0x7fffff相与,得到frac。

这里就涉及到精度问题,这个整数是否能用浮点数精确表示,不能的话取谁。先是用一个res把舍掉了E-23的情况存起来,再计算丢弃的位数部分dq,比较丢弃的dq和两个浮点数间隔2(E-24)大小,如果dq>2(E-24)那就说明暂时的结果加一res+1会比res更接近真实值,如果dq=2^(E-24),就根据向偶数舍入的原则,判断frac的最低位,如果是1(奇数)就进位,也就是res+1.其他情况就直接return res。

unsigned float_i2f(int x) {

  int s,E,exp,frac,e1,e2,dq,res;

  s=0;//默认正数

  if(x<0){

    x=-x;//若是负数,求补码

    s=1;//为负数再改

}

E=0;

if(x==0) return 0;

else if(x==0x80000000) return 0xcf000000;

    //非特殊情况

    while(1)//求阶码

    {

      if((x>>E)==1)//等于1时,说明左边只有一个1了

        break;

      E++;

    }

    exp=127+E;

    e1=E-23;

    e2=E-24;//M=x

if(E<=23)

  frac=(x<<(23-E));//左端右移填0

else frac=(x>>e1);//长度>23,就得往右移走一些

  frac=frac&0x7fffff;

  res=(s<<31)|(exp<<23)|frac;//舍去了后面几位的结果

  dq=x&~(0xfffffffff<<e1);//舍去的几位的值

if(E>23)

{

  int te=1<<e2,res2=res+1;

  if(dq>te)

  return res2;//如果舍去的 间隔的一半

//如果=一半,根据偶舍奇入的原则,x>>e1&0x1表示的就是frac的最低位

  else if((dq==te)&&(x>>e1&0x1)) return res2;

  return res;

}

return res;

}

1-15、float_twice:计算浮点数*2,返回浮点数格式的结果。

移位算出exp和frac,判断是否是特殊的+0,-0,NaN,无穷,是的话就直接返回;

如果是规格化数,2就直接是阶码E+1,即exp+1;

如果是非规格化数,就是尾数(frac)位左移一位,同时判断是否会有进位,进位exp就等于1;

最后把符号位s、exp、frac或运算合并起来,返回。

unsigned float_twice(unsigned uf) {

  int exp=(uf>>23)&0xff;//exp位

  int frac=uf&0x7fffff;//frac位

  int s=(uf>>31)&0x1;//符号位

  //+0,-0,NaN,无穷几个特殊值

  if(uf==0x0||uf==0x80000000||(exp==0xff))

    return uf;

  if(exp!=0x0)//规格化数

    exp++;

  else

  {

    exp=(frac>>22)&0x1;//非规格化数,frac最高位为1,exp才进1

    frac=frac<<1;//frac*2,左移1位

  }

  return s<<31|exp<<23|frac;//合并结果

}

2、用dlc检查。

3、用btest测试。

 528行的main函数这里不是我所填充的函数内容而是bits.c自带的,且变量设定但未被使用不影响正确性,不影响。

由此可得运行结果上是全部正确的。

 由上述验证得代码正确。

4、利用ishow和fshow文件帮助验证。

实验结果及分析:

经过./dlc和./btest的检验,程序无误,所有功能都已经实现。

同时可以发现,整数部分的函数只用操作符,运行很快出结果;float型的用到if,while等语句,运行时间比int的明显长。

收获与体会:

在本次实验中,对于计算机各个运算的底层实现有了更深刻的认识。对于最大、最小值等也有了一定的概念。

主要是题目不仅要求实现功能,还需要在限定数量和种类的运算符条件下实现,就加了很多难度。因为操作符的数量的限制,为了减少遍历的次数,就不得不用到分治。但是想要进行分治,结束的条件又很难想。很多不懂的知识点都需要硬啃,但是在学习期间和舍友们的讨论也收益颇丰。

参考资料:

csapp深入了解计算机系统配套datalab-handout实验_simenona的博客-CSDN博客

CSAPP - Bits LAB 位运算 - 知乎

CSAPP 2e Data Lab 笔记 – cyp0633's blog

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值