CSAPP实验一:位操作(Data Lab)

       本系列文章为中国科学技术大学计算机专业学科基础课《计算机系统》布置的实验,上课所用教材和内容为黑书CSAPP,当时花费很大精力和弯路,现来总结下各个实验,本文章为第一个实验——位操作(DataLab)。

一、实验名称:位操作

二、实验学时: 3

三、实验内容和目的

       1.了解各种数据类型在计算机中的表示方法

       2.掌握C语言数据类型的位级表示及操作

       3.本实验总共包括有关位操作的15个编程题

       4.你们的目标就是实现这15个编程题

       5.所有要实现的代码都在bits.c里面

四、实验原理

       1. <<:左移就是: 丢弃最高位,0补最低位 (算术和逻辑都是这样)

       2. >>:再说右移,明白了左移的道理,那么右移就比较好理解了.

       右移的概念和左移相反,就是往右边挪动若干位,运算符是>>.

       右移对符号位的处理和左移不同,对于有符号整数来说,比如int类型,右移会保持符号位不变,           例如:        

int i = 0x80000000;

i = i >> 1;    //i的值不会变成0x40000000,而会变成0xc0000000

       就是说,符号位向右移动后,正数的话补0,负数补1,也就是汇编语言中的算术右移。同样当移动的位数超过类型的长度时,会取余数,然后移动余数个位。

       负数10100110 >>5(假设字长为8位),则得到的是 11111101

       总之,在C中,左移是逻辑/算术左移(两者完全相同),右移是算术右移,会保持符号位不变 .实际应用中可以根据情况用左/右移做快速的乘 /除运算,这样会比循环效率高很多.

       3. !:逻辑非

       4. ˜ :位非

       5. & :位与

       6. ˆ:异或

       7. | :位或

五、实验步骤及结果

       1. tar xvf datalab-handout.tar解压代码,包含如下文件

       Bits.c:唯一需要修改的文件

       Btest.c :该文件的作用是对我们实现的bits.c功能的正确性行评估,

       README:关于btest的一些说明。

       Dlc:语法检查

       2. 打开bits.c文件,该文件包含了一个结构体team,我们需要首先补充该team中的数据

      3. Bits.c中包含需要实现的15个函数,文件中规定了实现每个函数需要的逻辑和算术操作符(规定数量)。

       只能使用规定的操作符! ˜ & ˆ | + << >>

       不能使用循环或者条件语句

       不能使用超过8位的常数(ff)

       4. 完成后用./dlc bits.c检查bits.c的语法是否正确,就是是否按照要求使用规定数量的操作符

       5. 使用终端,执行make clean && make btest编译文件,使用终端,执行./btest,检查自己是否做对了,最终运行./driver.pl获得打分

       6.实验思路以及程序代码(思路写在注释里面):

/* 
 * bitXor - x^y using only ~ and & 
 *   Example: bitXor(4, 5) = 1
 *   Legal ops: ~ &
 *   Max ops: 14
 *   Rating: 1
 * 只使用两种位运算实现异或操作。
 * 
  异或,相同为0,相异为1。相同的话,不是11就是00。
  x & y;就是找出11,(~x) & (~y);就是找出00。
  那么正确答案就是,既不是11,也不是00
 */
int bitXor(int x, int y)
{
  return ~(~x & ~y) & ~(x & y);
}
/* 
 * tmin - return minimum two's complement integer 
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 4
 *   Rating: 1
 * 使用位运算获取对2补码的最小 int 值
 * 
   Tmin就是最小的那个负数值,比较特殊,因为没有和它互为相反的正值。
   补码是 0x80000000, 所以直接对1进行左移就好了
 */
int tmin(void)
{
  return 0x1 << 31;
}
//2
/*
 * isTmax - returns 1 if x is the maximum, two's complement number,
 *     and 0 otherwise 
 *   Legal ops: ! ~ & ^ | +
 *   Max ops: 10
 *   Rating: 1
 * 通过位运算计算是否是补码最大值
 * 
   x判断是否为补码最大值,那么就需要经过某种特定的步骤把它转化为零。
   我们发现最大值+最小值=一个特殊的值,即0xFFFF FFFF,用二进制表示它会是全一,取反之后就是全零
 */

int isTmax(int x)
{
  int i = x + 1; // 这里取极端情况,当x就是最大值的时候,加一就变为最小值
  x = x + i;     // 最大值+最小值得到全一,0xFFFF FFFF
  x = ~x;        // 取反得全零。0x0000 0000
  // 然而到这里还没有结束,发现0xFFFF FFFF,也是特殊情况之一,它加一等于全零了,而他本身是全一的情况,所以我们还得排除这个0xFFFF FFFF。到此我们的答案就出来了~
  i = !i;    // 给i取反,一个非零的布尔值都是true,也就是1
  x = x + i; // 所以当给x+i的时候,要么加0要么就加1,它就会影响到下面的结果
  return !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,这里的奇数指的是位的阶级是2的几次幂。重在思考转换规律,如何转换为对应的布尔值。
 * 
   这个题目还是比较简单的,采用掩码方式解决。首先要构造掩码,使用移位运算符构造出奇数位全1的数 mask ,0xAA代表的是1010 1010,所以我们将8个A拼起来就行了,即0xAAAAAAAA
   然后获取输入 x 值的奇数位,其他位清零(mask&x),即x=x&0xAAAAAAAA,将这个数偶数位为变为0
   然后与 mask 进行异或操作,若相同则最终结果为0,然后返回其值的逻辑非。

 */
int allOddBits(int x)
{
  int mask = 0xAA | 0xAA << 8;
  mask = mask | mask << 16;
  x = x & mask;
  return !(mask ^ x);
}
/* 
 * negate - return -x 
 *   Example: negate(1) = -1.
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 5
 *   Rating: 2
 * 
 * 不使用 - 操作符,求 -x 值。这个题目是常识
 * 
 */
int negate(int x)
{
  return ~x + 1;
}
//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
 * 
 * 计算输入值是否是数字 0-9 的 ASCII 值
 * 
   x需要>=’0’且<=’9’,将x与临界点作差,然后判断符号位的为0还是1即可
   48 - 57为ascii码数字部分,右边用58是因为0的符号位算正数,所以要多减一次
 */
int isAsciiDigit(int x)
{
  return (!((x + ~48 + 1) >> 31)) & !!((x + ~58 + 1) >> 31);
}

/* 
 * conditional - same as x ? y : z 
 *   Example: conditional(2,4,5) = 4
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 16
 *   Rating: 3
 * 
 * 使用位级运算实现C语言中的 x?y:z 三目运算符
 * 
   如果根据 x 的布尔值转换为全0或全1更容易解决了,即 x==0 时位表示是全0的, x!=0 时位表示是全1的。
   这就是1-2行代码,通过获取其布尔值0或1,然后求其补码(0的补码是本身,位表示全0;1的补码是-1,位表示全1)得到想要的结果。
   然后通过位运算获取最终值。
 */
int conditional(int x, int y, int z)
{
  x = !!x;
  x = ~x + 1;
  return (x & y) | (~x & z);
}
/* 
 * 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 negX = ~x + 1;                                            //-x
  int addX = negX + y;                                          //y-x
  int checkSign = addX >> 31 & 1;                               //y-x的符号
  int leftBit = 1 << 31;                                        //最大位为1的32位有符号数
  int xLeft = x & leftBit;                                      //x的符号
  int yLeft = y & leftBit;                                      //y的符号
  int bitXor = xLeft ^ yLeft;                                   //x和y符号相同标志位,相同为0不同为1
  bitXor = (bitXor >> 31) & 1;                                  //符号相同标志位格式化为0或1
  return ((!bitXor) & (!checkSign)) | (bitXor & (xLeft >> 31)); //返回1有两种情况:符号相同标志位为0(相同)位与 y-x 的符号为0(y-x>=0)结果为1;符号相同标志位为1(不同)位与x的符号位为1(x<0)
}
//4
/* 
 * 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 
 * 使用位级运算求逻辑非 !
 * 
   逻辑非就是非0为1,非非0为0。利用其补码(取反加一)的性质,除了0和最小数(符号位为1,其余为0),外其他数都是互为相反数关系(符号位取位或为1)。
   0和最小数的补码是本身,不过0的符号位与其补码符号位位或为0,最小数的为1。利用这一点得到解决方法。
 */
int logicalNeg(int x)
{
  return ((x | (~x + 1)) >> 31) + 1;
}
/* 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表示加上符号位
}

//float
/* 
 * 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乘一个浮点数
 * 
   1.若原数为非规格化小数或0时,处理小数部分
 * if(exp_ == 0) return (uf<<1)|s_;
 * 2.若为NaN或INF时
 * if(exp_ == 255) return uf;
 * 直接返回。
 * 3.若为其他情况,即指数加一
 * ++exp_,
 * 4.若加了之后为INF时,保证其不为NaN,即小数部分全为0,
 * if(exp_ == 255) return 0x7f800000|s_;
 * 5.最后为一般情况,直接输出2*f
 * return (uf&0x807fffff)|(exp_<<23);
 */
unsigned floatScale2(unsigned uf)
{

  int exp_ = (uf & 0x7f800000) >> 23;
  int s_ = uf & 0x80000000;
  if (exp_ == 0)
    return (uf << 1) | s_;
  if (exp_ == 255)
    return uf;
  ++exp_;
  if (exp_ == 255)
    return 0x7f800000 | s_;
  return (uf & 0x807fffff) | (exp_ << 23);
}
/* 
 * 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
 * 
 * 将浮点数转换为有符号整数,float_f2i
 * 
 
 * 思路:
 * 先将浮点数分成三段,
 * 符号部分s_ = uf>>31,
 * 指数大小exp_ = ((uf&0x7f800000)>>23)-127,
 * 获取小数部分,并补上浮点数缺省的1,
 * frac_ = (uf&0x007fffff)|0x00800000。
 * 处理特殊情况:
 * 若全为0是返回0,
 * 若指数大于31,整数无法表示溢出返回0x80000000。
 * 若指数小于0,该数0<x<1返回0
 * 若指数部分大于23则将小数部分向左移动frac_ <<= (exp_ - 23) ,exp_代表指数大小。
 * 若指数部分小于23则将小数部分向右移动frac_ >>= (23 - exp_) ,exp_代表指数大小。
 * 考虑最后符号,正数转换为负数不会产生溢出。
 * 若frac_为正数,则根据s_调整正负输出即可。
 * 若frac_为负数,唯一正确情况为0x80000000。


 */
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_ > 31)
    return 0x80000000;
  if (exp_ < 0)
    return 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 - 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
 

 * 一般情况下,
 * 把x当成浮点数的阶码就可以了,加 127 得到指数阶码,由于小数点后面都是 0,只需左移指数部分
 * 考虑特殊情况,
 * 指数exp<-127和 exp>128,分别返回0和0x7f800000
 */
unsigned floatPower2(int x)
{
  if (x < -127)
    return 0;
  if (x > 128)
    return 0x7f800000;
  x += 127;
  x = x << 23;
  return x;
}

六、运行测试程序结果

猜你喜欢

转载自blog.csdn.net/qq_35739903/article/details/119599979