CMU-datalab做后总结

datalab实验记录

本博 主要参考一位[知乎博主](https://zhuanlan.zhihu.com/p/59534845),第一次实验,特此记录一下。

本次实验记录是基于 CSAPP 3.0,实验日期始于:2019-9-20
实验所需压缩包请访问 CSAPP_LAP

该实验需要在Linux环境下进行操作,所以大家可以安装VMware来创建Linux环境,我自己用的Ubuntu镜像,比较好用。
另外,有些人一开始打开压缩包是懵逼的,因为有好多文件,其实里面可用的就几个bits.c进行实验(即用c编写代码就在这个地方)每次修改完之后要记得保存

driver.pl 在终端输入 ./driver.pl 的命令即可得到运行后的得分情况,有的人是运行btest.c文件,但我没用实现。相比较于运行btest.c,上述方法相较简单一些

实验打算以下的框架来记录

题目列表
loop //详细解释
{
题解
思考
代码
}
结果
关于本次实验的思考

题目列表

在这里插入图片描述

题解

bitXor(x,y)

  • bitXor - x^y using only ~ and &
  • Example: bitXor(4, 5) = 1
  • Legal ops: ~ &
  • Max ops: 14
  • Rating: 1

~& 两个运算符来实现异或操作,即x^y

思考

这题比较简单,有关数理逻辑的内容。
异或,即当 ab 不相同时为真,相同时为假,即 a^b=(¬a ∧ b) ∨ (a ∧¬b) ,因为只提供 ~& 两个运算符来实现异或操作,所以需要将 改成 ,这里用到了分配律+摩根定律,不熟悉的可以网上查找。

这里直接给出结果 :

  • a^b=(¬a ∧ b) ∨(a ∧¬b)= (¬a∨a)∧ (¬a∨¬b)∧ (b∨a) ∧ (¬b∨b)
    = (¬a∨¬b)∧ (b∨a)=¬ (a∨b) ∧ (¬ (¬ b∨¬ a))
代码
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

返回最小的二进制补码

思考

这题送分题,只需将 1 移到最高位即可。

代码
int tmin(void) {

  return 1<<31;

}

____________________________________________________________________

isTmax(x)

  • isTmax - returns 1 if x is the maximum, two’s complement number,
  • and 0 otherwise
  • Legal ops: ! ~ & ^ | +
  • Max ops: 10
  • Rating: 1

判定值 x 是否为补码的最大值 Tmax,是则返回1,反之为0。

思考

这一题我认为是 Rating:1 中最难的一题,甚至比有些 Rating:2还难。
首先要先明白

  • Tmax=0111 1111 1111 1111 1111 1111 1111 1111

要想使返回值为1,假使这时 x=Tmax (我认为这是一种很重要的思想,将 x假设为你想要返回为真的那个值,然后一步步往下去想) 则要将 x0上去想,也就是 x 怎么才能变成0,为0后再取非 ,则得到结果 1

这里提出一种方法,当 x =Tmax 时,则 x+x+1+1=0x转换为了0),但同时也要注意到当 x=0xFFFFFFFF 即全1时,上述方法同样成立,因此为了得到返回值1,就需要排除x=0xFFFFFFFF (实质就是寻找一种方法,怎样才能让 0xFFFFFFFF 转换为非0的同时,Tmax 能够转换为0,那样再与高亮的式子相加取反,便能得到结果)

  • 因为
    temp=0xFFFFFFFF    ~temp=0     !( ~temp)=1
    temp=Tmax       ~temp=Tmin    !( ~temp)=0

! (x+x+1+1+!(~x)) 即可得到结果

代码
int isTmax(int x) {
  return !(x+x+1+1+!(~x));
}

____________________________________________________________________

allOddbits(x)

  • 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

返回1,如果所有的奇数位上的数字均为1

思考

最直接的想法是将 x0xAAAAAAAA进行 & 运算,得到 x & 0xAAAAAAAA ,即将 x 所有奇数位的位值分离出来,此时偶数位位值均为 0

为了进一步确认奇数位上所有的位值是否全为 1 ,再将 x0xAAAAAAAA进行 ^ 运算,返回 0 ,再取非 ,即可得到真值 1
那么现在的问题就是怎么去构造一个0xAAAAAAAA ,因为程序所允许输入的范围只是 0~0xff

这里提出一种方法:先取初值 0xAA ,然后对其不断移位累加求和,即可得到 0xAAAAAAAA,具体实现参见代码。

代码
int allOddBits(int x) {
  int temp=0xaa+(0xaa<<8);//为了构造出0xAAAAAAAA
  temp=temp+(temp<<16);
  x=temp&x;//偶数位全为零,只保留奇数位上的数值
  return !(temp^x);
  //return !((temp&x)^temp);
}

____________________________________________________________________

nagate(x)

  • negate - return -x
  • Example: negate(1) = -1.
  • Legal ops: ! ~ & ^ | + << >>
  • Max ops: 5
  • Rating: 2

这一题也很简单,怎么取负数,送分题

思考

方法就是直接取反+1

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

____________________________________________________________________

isAsciiDigit(x)

  • 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

也就是判断 x 是否在 [0x30,0x39] 内。

思考

很容易可以发现:
          0x30             0x39
—————————————————————————————————————
     符号          符号              符号
x-0x30<0  1    x-0x30>=0  0        x-0x30>0   0
x-0x39<0  1    x-0x39<=0  0 or 1      x-0x39>0   0

可以发现当我们试图通过 减去0x30 0x39来判定范围时,符号位可能出现两种情况,这样就给判断增加了难度(因为我们只想出现唯一的一种情况,不想进行选择判断), 我一开始思考的时候就一直陷入了这个误区,之后才发现这个问题。

于是我们不去减0x39 ,而是去减0x3a,这样在区间内的判断就变得唯一了。
         0x30             0x3a —————————————————————————————————————
     符号          符号              符号
x-0x30< 0  1    x-0x30>=0   0         x-0x30>0   0
x-0x3a< 0  1    x-0x3a< 0  1        x-0x3a>=0   0

然后对x-0x30的值进行取反再取符号位,将两式的符号位进行 & 操作,即可得到结果。

代码
int isAsciiDigit(int x) {
  int low=(~(x+(~0x30+1))>>31)&0x1;//x-0x30>=0 并取其符号位
  int high=((x+(~0x3a+1))>>31)&0x1;//x-0x3a<0  并取其符号位
  return low&high;
}

____________________________________________________________________

conditional(x, y, z)

  • conditional - same as x ? y : z
  • Example: conditional(2,4,5) = 4
  • Legal ops: ! ~ & ^ | + << >>
  • Max ops: 16
  • Rating: 3

进行选择输出,x=0时为假,返回z的值,反之为真,返回y的值。实质就是c语言中 x ?y:z的实现。

思考

首先,要明白的是,x等于多少是不重要的,它无非会有两种结果,0非0 ,所以第一步就要将0取非,来得到一位值10, 即 x=!x

这一题实质上是让我们用他所给的操作符来实现一个选择判断,我刚开始想的是,是否能用这八个操作符来实现一个类似于 if…else的结构,但发现很难实现。

后面就考虑了以下的做法:能够输出一个值的同时,势必能让另一个值为零。也就是在yz前面加上一个系数 (这种说法不是很准确,但相对容易明白),一个系数为全1的同时,另一个系数势必为全0

可以想到 ~0=0xFFFFFFFF 可以实现这个操作,取反后即为0。但既然有x,势必是因为x的值 才决定说取y还是取z,因此,这里就要将x 与系数进行挂钩。上面已经得知x=!x后:
      x        !x       x+0xFFFFFFFF
—————————————————————————————————————
     0          1          0
     非0         0        0xFFFFFFFF

那么假设x非零时,我想输出 y ,则此时y的系数应该为0xFFFFFFFF,即可表示为 (x+(~0))&y,那么此时有关z的式子就为 ( ~ (x+(~0))&z

代码
int conditional(int x, int y, int z) {
  x=!x;
  return (   (  (~0+x)  &y) + (  (  ~(~0+x)  )  &z)   );
}

____________________________________________________________________

isLessOrEqual(x,y)

  • isLessOrEqual - if x <= y then return 1, else return 0
  • Example: isLessOrEqual(4,5) = 1.
  • Legal ops: ! ~ & ^ | + << >>
  • Max ops: 24
  • Rating: 3

判断两个数值的大小,如果x <= y 就返回1

思考

对于这题,最直接的考虑肯定是y-x>=0,通过符号位进行判断即可。
但是,这么做是有问题的,我一开始就是这么考虑到,所以一直错,最后参考了别的博主的思考,才明白我少考虑了内容。
xy 同号时当然可以这么做,但当它俩异号时,这么做就会有溢出的风险。

因此,需要考虑两种情况:(signx signy分别为x y的符号位)
同号 :signx signy      执行操作:y-x>=0
   0   0
   1   1
异号:0   1 错误 
   1   0 正确
但我们发现同号时用操作符不是很方便得到1,因此这里做一个小小的改变,对y进行取反 (你会发现取反y,而不是取反x,会对下一步的操作造成影响)。那么符号位就会发生相应的变化,相当于signy全部取非了。
同号 :signx signy      执行操作:y-x>=0
   0   1
   1   0
异号:0   0 错误 
   1   1 正确
注意高亮部分,你就会发现为什么取反y,而不是取反x了。

该题实质上也是一个选择判断,因此我们也考虑同号异号两种选择,一个系数为的同时,另一个系数必然为。可以发现,同号异号的符号位正好分为两种情况,那我们对signxsigny执行**^** 操作,即可得出同号为1,异号为0
                          系数
同号操作: (  (  ~ (y+(~x+1) )  ) >>31 ) & 0x1   signx^signy
异号操作:signx & signy               !(signx^signy)
再加上相应系数,即进行 & 操作。

代码
int isLessOrEqual(int x, int y) {
  int signx=(x>>31)&0x1;//x的符号位
  int signy=((~y)>>31)&0x1;//y先取反,再取其符号位
  return ((signx^signy)&(((~(y+(~x+1)))>>31)&0x1))
  			+
  			(!(signx^signy)&(signx&signy));
}

____________________________________________________________________

logicalNeg(x)

  • 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

用其他七个操作符来实现 取非 的操作,不允许使用

思考

该题也进行了选择判断,一开始我也会是懵逼的,但因为有了前几题的经验,我就想x+(~0)试一试,看看会造成什么影响:
   x        x符号位          x+(~0)符号位  —————————————————————————————————————
  0          0              1
    |—— 正数   0              0
  非0|                       
    |—— 负数   1             1or0

惊喜出现了,我们发现只有高亮的那一组会出现 0 1 ,那么就考虑将x符号位取反为1,再将两个符号位进行**&**操作,不就可以返回真值了吗。

代码
int logicalNeg(int x) {
  return (    ( (~x)>>31)  &0x1  )&(   ((~0+x)>>31)  &0x1  );
}

____________________________________________________________________

howManyBits( x)

  • 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

求值:“一个数用补码表示最少需要几位?”

思考

这一题我认为是所有题目当中最难的一道,没有算法基础的很难想到,我没想出来,因此参考大佬的做法,写出我自己的理解。
首先要理解题目对位数的定义,负数可以就按正常补码的理解,而正数则要在正常的基础上加上一个符号位0,例如
howManyBits(12) = 5—>0 1 1 0 0,为五位,而非四位。

为了便于处理,我们将负数统一取反,之所以可以这么做是因为,为了确定负数用补码表示最少需要几位,则需要找到负数中从高位开始第一个为零的位,取反后则需要找到第一个为1 的位,实现的效果其实相一致 (实在不明白可以在纸上画一画)

就像上面说的,我们需要找到第一个 1 的位置,得出从该位置算起有多少位,再加上一个符号位,就可以得出一个数用补码表示最少需要几位。
但难点在于我怎么去确定二进制数列中第一个1的位置。大佬写的是一种放缩的思维,首先我要假设一下一系列操作均成立:
假设1在高16位中,然后将x右移16位,则该补码至少需要16位。
假设1在高8位中,然后将x右移8位,则该补码至少需要16+8位。
假设1在高4位中,然后将x右移4位,则该补码至少需要16+8+4位。
假设1在高2位中,然后将x右移2位,则该补码至少需要16+8+4+2位。
假设1在高1位中,然后将x右移1位,则该补码至少需要16+8+4+2+1位。
假设1在高1位中,则x=1,则该补码至少需要16+8+4+2+1+1位。
最后不要忘记符号位,再加上1。
这应该是涉及到一些算法,不理解的可以查询相关算法。

代码
int howManyBits(int x) {
  int b16,b8,b4,b2,b1,b0;
  //int sign=x>>31;
  //x = (sign&~x)|(~sign&x);
  int sign=(x>>31)&0x1;
  x=(((~0)+sign)&x)+((~((~0)+sign))&(~x));
  b16=(!!(x>>16))<<4;
  x=x>>b16;
  b8=(!!(x>>8))<<3;
  x=x>>b8;
  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;
}

____________________________________________________________________

floatScale2( uf)

  • 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

求2乘以一个浮点数

思考

这一题实际上还比较好做。但你首先要明白的是,题目给你的是一个无符号整数,但你要将它看成一个补码,然后将这个补码乘以2后得到结果输出。对于NaN,则输出它本身。
sign         exp           frac
—————————————————————————————————————
              N
              a
              N
0/1    111...111    000...000  INF
0/1    111...110    111...111  规格化最大值
          .            .
          .            .   
0/1    000...001    000...000   规格化最小值
0/1    000...000    111...111   非规格化最大值
          .            .
          .            .
0/1    000...000    000...000  非规格化最小值0

可以很直观地看出要实现2×f,
1.当255>exp>0时,只需要exp+=0x800000,便可完成此操作。但不要忘了,随着exp的增加,exp可能会变为全1,这时,它就变成INF,则需要将frac赋值为0。

2.当exp=0时,则需要将frac左移一位。
    我当初对exp=0的情况相当困惑,因为假如frac=0x7FFFFF时,即frac为全1时,frac左移后低位补零,那岂不是变小了。而实际情况则是frac左移后:frac=0xFFFFFE(即 1 111 1111 1111 1111 1111 1110),原来23位的frac变成了24位(即exp=0x800000了,计算方法也改变了)。
    
3.当exp=255时,则返回它本身。

代码
unsigned floatScale2(unsigned uf) {
  unsigned sign=uf&0x80000000;
  unsigned exp=uf&0x7f800000;
  unsigned frac=uf&0x7fffff;
  //if((!(exp^0x7f800000))==0) right
  if(exp^0x7f800000)
  {
	if(!exp)
	{
		frac<<=1;
	}
	else
	{
		exp+=0x800000;
		if((exp^0x7f800000)==0)
		{
			frac=0;
		}
	}
  }
  return sign|exp|frac;  
}

____________________________________________________________________

floatFloat2Int( uf)

  • 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型,但我们要把他看成浮点型,容易搞混的话,最好的方法就是不管它是无符号 int型,直接将其看做浮点型来进行处理。我们先给出公式:M * (2^E)
sign         exp           frac
—————————————————————————————————————
              N
              a
              N
0/1    111...111    000...000  INF
0/1    111...110    111...111  规格化最大值
          .            .
          .            .   
0/1    000...001    000...000   规格化最小值
0/1    000...000    111...111   非规格化最大值
          .            .
          .            .
0/1    000...000    000...000  非规格化最小值0

一定要留意有正数负数两种情况,虽然大部分操作过程相一致。
通过简单的计算我们可以得知E=((uf&0x7f800000)>>23)-127,而且非规格化数值是一定小于0的(正数,自己可以乘乘看),也就是int型是无法表示的,太小了。所以能表示的范围只有规格化数字。那么M=frac_=frac+0x800000。
1.当E>30时,即使frac=1,符号位也会被覆盖,所以不可以,返回0x800000。
2.当E<0时,(1右移x位,x>0,结果为0)则返回0。
3.当0<=E<=30时,还需要进行两种考虑,即frac_左移右移的问题:
   *当exp_>23时,则将frac_向左移动exp_-23位
    *当exp_<23时,则将frac_向右移动23-exp_位

最后,首先把小数部分(23位)转化为整数(和23比较),然后判断是否溢出:如果和原符号相同则直接返回,否则如果结果为负(原来为正)则溢出返回越界指定值0x80000000u,否则原来为负,结果为正,则需要返回其补码(相反数)。

代码
int floatFloat2Int(unsigned uf) {
   int s_    = uf>>31;
  int exp_  = ((uf&0x7f800000)>>23)-127;
  int frac_ = (uf&0x007fffff)|0x00800000;
  if(!(uf&0x7fffffff)) return 0;

  if(exp_ > 30) return 0x80000000;//当E>30时,即使frac=1,符号位也会被覆盖,所以不可以,返回0x800000。
  if(exp_ < 0) return 0;//(1右移x位,x>0,结果为0)则返回0。

  if(exp_ > 23) frac_ <<= (exp_-23);
  else frac_ >>= (23-exp_);

  if(!((frac_>>31)^s_)) return frac_;
  else if(frac_>>31) return 0x80000000;
  else return ~frac_+1;
}

____________________________________________________________________

floatPower2( x)

  • 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

求 2.0^x

思考

这一题我没有特别理解,看不懂的再看看别人的。
首先得到偏移之后的指数值e(即为exp,这里要注意exp的范围是 (0,255) ),如果x+127还小于0,那它本身一定非常小,2.0^x也会很小。
1.如果e小于等于0(为0时,结果为0,因为2.0的浮点表示frac部分为0)。
2.如果e大于等于255则为无穷大或越界了。
3.否则返回正常浮点值,frac为0,直接对应指数即可。

代码
unsigned floatPower2(int x) {
  int INF=0xff<<23;
  int exp=x+127;
  if(exp<=0)  return 0;
  if(exp>=255)  return INF;
  return exp<<23;
}

结果

最后一题参考大佬解法,他运行也出现了出错的问题,所以如果报错最后一题,那就多运行几次。
在这里插入图片描述

关于本次实验的总结

新晋研究生一枚,荒废了大学四年,好不容易考上研究生,真的打算洗心革面了。
这是我CSAPP实验的第一次实验,从2019-9-20左右开始,到2019-10-10完成,之所以拖了很久,是因为做了三四题的样子就突然卡克了,完全没有思路,本科玩了四年,懒散成性,就扔在一边好几天。不知道是不是良心突然发现,就又花了近一个星期耐心的做了下来。

但能力毕竟有限,四星题基本都是大佬的做法,我也仅能学习学习,尤其howManyBits( x)这题,没有一定的基础,真的是想破脑袋也做不出来。但看完之后,实际上float的几题难度都不是很大的,只要不进入一些误区,是能想出来的。

主要收获的是二星三星基本就都是自己独立完成的,刚开始真的认为自己做不出来,但当你一上午或者一下午坐在实验室就搞这些题时,真的慢慢搞出来时,对一个渣渣来说,那种成就感还是很强烈的。

所得

  • 实验一定要自己做,即使完全没有思路,也不能完全照搬别人的代码,要先试着去看看别人的思路对你有什么启发,再自己去实现出来。
  • 一定要坐得住,实际上大部分内容不是很难得,多想想总做得出来。
  • 实验必须总结,定期回顾,更新思路。
  • 3
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值