CSAPP Experiment 1: Bit Operation (Data Lab)

       This series of articles is an experiment arranged by the basic course "Computer System" of the computer major of the University of Science and Technology of China. The teaching materials and content used in the class are the black book CSAPP. At that time, it took a lot of energy and detours. Now I will summarize each experiment. This article is the first An experiment - bit manipulation (DataLab).

1. Experiment Name: Bit Operation

2. Experimental hours: 3

The content and purpose of the experiment

       1. Understand how various data types are represented in computers

       2. Master the bit-level representation and operation of C language data types

       3. This lab includes a total of 15 programming questions about bit manipulation

       4. Your goal is to achieve these 15 programming problems

       5. All the code to be implemented is in bits.c

4. Experimental principle

       1. <<: Left shift is: discard the highest bit, 0 complement the lowest bit (this is the case for arithmetic and logic)

       2. >>: Let's move to the right. If you understand the reason for moving to the left, then moving to the right is easier to understand.

       The concept of right shift is the opposite of left shift, that is to move a few bits to the right, the operator is >>.

       Right shift handles the sign bit differently than left shift. For signed integers, such as int types, right shift will keep the sign bit unchanged, for example:        

int i = 0x80000000;

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

       That is to say, after the sign bit is moved to the right, 0 is added for positive numbers, and 1 is added for negative numbers, which is the arithmetic right shift in assembly language. Also when the number of bits shifted exceeds the length of the type, the remainder is taken, and then the remainder is shifted.

       The negative number 10100110 >> 5 (assuming the word length is 8 bits), then the result is 11111101

       In short, in C, left shift is logical/arithmetic left shift (both are exactly the same), and right shift is arithmetic right shift, which will keep the sign bit unchanged. In practical applications, left/right shift can be used for fast multiplication according to the situation / division operation, which is much more efficient than looping.

       3. !: Logical NOT

       4. ˜ : bit not

       5. & : bitwise AND

       6. ˆ: XOR

       7. | : bitwise OR

5. Experimental steps and results

       1.  tar xvf datalab-handout.tar decompression code, including the following files

       Bits.c: the only file that needs to be modified

       Btest.c : The role of this file is to evaluate the correctness of the bits.c function we implemented,

       README: Some notes about btest.

       Dlc: grammar check

       2.  Open the bits.c file, which contains a structure team, we need to supplement the data in the team first

      3.  Bits.c contains 15 functions that need to be implemented, and the file specifies the logical and arithmetic operators (specified number) required to implement each function.

       Use only the specified operators! ˜ & ˆ | + << >>

       You cannot use loops or conditional statements

       Constants with more than 8 bits cannot be used (ff)

       4.  After completion, use ./dlc bits.c to check whether the syntax of bits.c is correct, that is, whether the specified number of operators are used as required

       5.  Use the terminal, execute make clean && make btest to compile the file, use the terminal, execute ./btest , check if you did it right, and finally run ./driver.pl to get the score

       6. Experimental ideas and program code (the ideas are written in the comments):

/* 
 * 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;
}

6. Results of running the test program

Guess you like

Origin blog.csdn.net/qq_35739903/article/details/119599979