【CSAPP实验】Manipulating Bits(比特操作)

又到寒假了!可以开始营业了!营业第一天的文章,更一下很久之前CSAPP实验1:比特操作)

因为当时按照学号随机分题,故并非所有函数都是我的实验中需要实现的。但后续期末复习时整理了一些自己认为比较有意思的实现函数,也会放在后面和大家分享

我实现的

当时沉迷使用英语写注释,显得有一些像加密通话了😂
TIPS: 注释里面#这个符号表示 the number of (…的数量)


编程要求:

	将“return”语句替换成一行或者多行实现函数功能的C代码。
	你添加/修改的代码必须符合下面的风格:

  int Funct(arg1, arg2, ...) {
      /* 简要描述你是如何实现该函数功能的 */
      int var1 = Expr1;
      ...
      int varM = ExprM;

      varJ = ExprJ;
      ...
      varN = ExprN;
      return ExprR;
  }

	每一个“Expr”是一个仅仅使用下面内容的表达式:
  1. 0~2550xFF)的整数常数。不允许使用大的常数,例如0xffffffff2. 函数的参数和局部变量(不允许使用全局变量)。
  3. 规约的整数运算符" ! ~ "
  4. 二进制整数运算符“ & ^ | + << >> ”

	一些函数的实现,限制了能够使用的运算符。
	每一个“Expr”表达式可能包含多个运算符。没有要求你必须每行只用一个运算符。

	下列事项被严格禁止:
  1. 使用任何控制类的语句,例如if, do, while, for, switch, 等等。
  2. 定义或者使用任何宏。
  3. 在此文件中新增定义任何额外的函数。
  4. 调用任何函数。
  5. 使用任何其他的运算符,例如 &&, ||, -, 或者 ?:
  6. 使用任何形式的强制类型转换。

  你需要假设你的电脑:
  1. 使用2的补码,32位表示的整数。
  2. 右移运算是算术右移运算。
  3. 如果移位次数超过了字长,那么将会产生不可预测的结果。

可以接受的编程风格的示例:
  /*
   * pow2plus1 - returns 2^x + 1, where 0 <= x <= 31
   */
  int pow2plus1(int x) {
     /* exploit ability of shifts to compute powers of 2 */
     return (1 << x) + 1;
  }

  /*
   * pow2plus4 - returns 2^x + 4, where 0 <= x <= 31
   */
  int pow2plus4(int x) {
     /* exploit ability of shifts to compute powers of 2 */
     int result = (1 << x);
     result += 4;
     return result;
  }


注意:
  1. 首先使用dlc.exe检查你的bits.c是否符合编程要求。
  2. Each function has a maximum number of operators (! ~ & ^ | + << >>)
     that you are allowed to use for your implementation of the function.
     The max operator count is checked by dlc. Note that '=' is not
     counted; you may use as many of these as you want without penalty.
  3. 使用btest检查你的函数是否功能正确。
  4. The maximum number of ops for each function is given in the
     header comment for each function. If there are any inconsistencies
     between the maximum ops in the writeup and in this file, consider
     this file the authoritative source.
#endif

/*
 * 步骤3: 根据上面的编程要求,修改下面的函数。
 *
 *   重要!为了避免很差的成绩:
 *   1. 使用dlc.exe检查你的编程风格是否符合要求。
 *   2. 使用btest检查你的函数是否功能正确。请注意在Tmin和Tmax附近的特例是否正确。
 */



/*
 * bitAnd - x&y using only ~ and |
 *   Example: bitAnd(6, 5) = 4
 *   Legal ops: ~ |
 *   Max ops: 8
 *   Rating: 1
 */
 //按位与(用与和或逻辑搭建,德摩根定律推气泡即可)
int bitAnd(int x, int y) {
  int x_inv=~x;
  int y_inv=~y;
  return ~(x_inv|y_inv);

}




/*
 * bitXor - x^y using only ~ and &
 *   Example: bitXor(4, 5) = 1
 *   Legal ops: ~ &
 *   Max ops: 14
 *   Rating: 2
 */
 //按位异或,用与、非逻辑就可以搭建出来
 //(recall:与、非/或、非可以搭建出所有的逻辑和算数运算)
int bitXor(int x, int y) {

  int x_inv=~x;
  int y_inv=~y;
  int nand1=~(x_inv & y);
  int nand2=~(x & y_inv);
  int result=~(nand1 & nand2);
  return result;

}



/*
 * getByte - Extract byte n from word x
 *   Bytes numbered from 0 (LSB) to 3 (MSB)
 *   Examples: getByte(0x12345678,1) = 0x56
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 6
 *   Rating: 2
 */
 //本题要求从一个字x中提取字节n(从0开始索引)
int getByte(int x, int n) {

  int get_opt=0xFF;
  int opt=get_opt<<(8*n);
  return (0xFF & ((opt & x)>>(8*n)));

}


/*
 * bitMask - Generate a bitmask consisting of all 1's
 *   from lowbit to highbit and 0's everywhere else.
 *   Examples: bitMask(5,3) = 0x38
 *   Assume 0 <= lowbit <= 31, and 0 <= highbit <= 31
 *   If lowbit > highbit, then mask should be all 0's
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 16
 *   Rating: 3
 */
 //题目要求构造一个
 //从参数highbit到lowbit全为1,其余位全为0的bitmask,
 //如果lowbit>highbit,这个mask的32位全0
int bitMask(int highbit, int lowbit) {
  int neg_lowbit=~(lowbit)+1; // = -lowbit
  int Neg=0x01<<31;// 0x8000_0000
  int wrong=!(!((highbit+neg_lowbit) & (Neg))); // if wrong==0 highbit>=low, else high<low
  int num=(highbit+neg_lowbit+1) & ((!wrong<<31)>>31); // # of 1
  int neg_num=~(num)+1;
  int masking_ones=((0x01<<31)>>31);
  int fine=!(!((32+neg_num))); // if num ==32, fine=0 
  int zeros=(((0x01<<31)>>(31+neg_num)) & ((fine<<31)>>31));// generate # 1s
  int ones=zeros ^ masking_ones;
  int shamt=lowbit;
  int mask=ones<<shamt;

  return mask ;

}



/*
 * isPositive - return 1 if x > 0, return 0 otherwise
 *   Example: isPositive(-1) = 0.
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 8
 *   Rating: 3
 */
int isPositive(int x) {

  int Neg=0x01<<31;
  int flag=!(x & (Neg)); // if flag==1 is positive or zero
  return !(!((flag<<31)>>(31) & (x)));

}

我整理的(来源互联网各处)


/*bitXor*/
int bitXor(int x, int y)
{
      int a = ~(x&~y);
      int b = ~(~x&y);
      return ~(a&b);
}

* tmin - return minimum two's complement integer 
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 4
 *   Rating: 1
 *题意是要求返回一个二进制里的最小值。整个程序运行在32位系统上,所以我们要找32位里二进制的最小值。
 */
int tmin(void) {
    int a = 1;
    return a << 31;
}

/*
 * isTmax - returns 1 if x is the maximum, two's complement number,
 *     and 0 otherwise 
 *   Legal ops: ! ~ & ^ | +
 *   Max ops: 10
 *   Rating: 1
 */
int isTmax(int x) {
    int a=x+1;
        x=a+x;
        x=~x;
        a=!a;
        res=x+a;
    return !res
}

/* 
 * 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
 */
int allOddBits(int x) {
  //这里我们不能直接用0xaaaa aaaa,所以先移位把它搞出来
  int a = 0xaa;
  int a_8 = a<<8;//0x0000aa00
  int  low_16  = a|a_8;//0x0000aaaa;
  int high_16 = low_16<<16;//0xaaaa0000
  int num = low_16 | high_16;//0xaaaaaaaa
  int check = (x&num)^num;
  //如果check是0 则满足要求 返回1
  return !check;
}

 /* negate - return -x 
 *   Example: negate(1) = -1.
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 5
 *   Rating: 2
 *不管正数还是负数,我们对其取反再加1都等于它的相反数。所以答案就是
 */
int negate(int x) {
  return ~x + 1;
}

/* 
 * 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就返回1;否则,返回0。
		回到这个题x需满足条件:0x30=<x=<0x39,我们将它拆分开,0x30=<x且x=<0x39。即

		x-0x30>=0------>(x+~0x30)=a

		0x39-x>=0------>(0x39+~x)=b
		此时我们只需要判断符号位了,当a,b均大于等于0时,a和b的符号位肯定为0。所以我们分别将a,b右移31位,当a,b均为0时,判断为真。则答案为:
 */

int isAsciiDigit(int x) 
{
   return !((x + ~0x30)>>31) | ((0x39 + ~x)>>31));
}

/* 
 * bitAnd - x&y using only ~ and | 
 *   Example: bitAnd(6, 5) = 4
 *   Legal ops: ~ |
 *   Max ops: 8
 *   Rating: 1
 */
int bitAnd(int x, int y) {
  return ~((~x)|(~y));
}


/* 
 * getByte - Extract byte n from word x
 *   Bytes numbered from 0 (LSB) to 3 (MSB)
 *   Examples: getByte(0x12345678,1) = 0x56
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 6
 *   Rating: 2
 */
int getByte(int x, int n) {
  return (x>>(n<<3))&0xff;
}

/* 
 * logicalShift - shift x to the right by n, using a logical shift
 *   Can assume that 0 <= n <= 31
 *   Examples: logicalShift(0x87654321,4) = 0x08765432
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 20
 *   Rating: 3 
 第三题开始就有些难度了,实现一个integer的逻辑右移。你可能会觉得是简单的>>n。那你就naive了,integer右移分为逻辑右移和算数右移,算数右移就是简单的>>,高位会用最高有效位填充,但是这里是实现逻辑右移,高位都用0填充。

所以我们需要先计算出,原最高位在右移了n次之后在哪里,也就是31-n,但是这里不能用减法,只能用加法,所以就是y=31+(n的负数),n的负数位运算表示为~n+1。然后把x右移n位,去&一个低y全为1的数,这个数为(1<<(y+1))-1。代码中的写法可以防止溢出。
 */
int logicalShift(int x, int n) {
  int y = 32+(~n);
  return (x>>n)&((1<<y)+(~0)+(1<<y));
}


/*
 * bitCount - returns count of number of 1's in word
 *   Examples: bitCount(5) = 2, bitCount(7) = 3
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 40
 *   Rating: 4
 这题应该是这次作业里最难的题了,只用40次操作,并且不能用循环和if,计算x二进制表示中1的个数。首先需要设计5个常数,分别为0x55555555,0x33333333,0x0f0f0f0f,0x00ff00ff,0x0000ffff。

为什么这么设计呢,可以把5个数字的二进制写出来,分别是间隔1个0,2个0,4个0,8个0,16个0

然后下面的计算方法,和间隔对应,分别右移1,2,4,8,16次。

如何理解呢,如果只给你一个只有2位的二进制数x,以及01,怎么计算里面1的个数呢,是不是做(x&1)+((x>>1)&1)呢?通过移位,把高位的1,移到低位求和。

32位的数字就可以看成是16个2位的,之后就可以等价看作,一个16位的数字,分成8段,做上面第二个计算操作。

总的来看这个操作就是把高位的1往低位移动,然后类似分治,分成多段的移动。非常nice的一个题哦。
 */
int bitCount(int x) {
  int _mask1 = (0x55)|(0x55<<8);
  int _mask2 = (0x33)|(0x33<<8);
  int _mask3 = (0x0f)|(0x0f<<8);
  int mask1 = _mask1|(_mask1<<16);
  int mask2 = _mask2|(_mask2<<16);
  int mask3 = _mask3|(_mask3<<16);
  int mask4 = (0xff)|(0xff<<16);
  int mask5 = (0xff)|(0xff<<8);
  
  int ans = (x & mask1) + ((x>>1) & mask1);
  ans = (ans & mask2) + ((ans>>2) & mask2);
  ans = (ans & mask3) + ((ans>>4) & mask3);
  ans = (ans & mask4) + ((ans>>8) & mask4);
  ans = (ans & mask5) + ((ans>>16) & mask5);

  return ans;
}

/* 
 * bang - Compute !x without using !
 *   Examples: bang(3) = 0, bang(0) = 1
 *   Legal ops: ~ & ^ | + << >>
 *   Max ops: 12
 *   Rating: 4 
     对0返回1,其他数字返回0,这就要观察0和其他数字的性质有啥区别了。

发现只有0和-0的二进制中,最高位都不是1,其他数字x,x或者-x中总有一个的最高位为1,这个性质就可以很好的解决这个题,把x和-x或起来之后就解决了。
 */
int bang(int x) {
  return ((~(x|(~x+1)))>>31)&1;
}

/* 
 * fitsBits - return 1 if x can be represented as an 
 *  n-bit, two's complement integer.
 *   1 <= n <= 32
 *   Examples: fitsBits(5,3) = 0, fitsBits(-4,3) = 1
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 15
 *   Rating: 2
 求x是否在n位补码的范围内。要注意,x是32位表示,所以最高位在32位,而不是在第n位,所以不能直接右移解决问题。

要写几个数字仔细观察,如果是非负数,在n位范围内,应该只有最低n-1位里有1,

如果是负数,应该是只有最低n-1位里有0。

所以对范围内的数字右移n-1位之后,应该要么是全1,要么是全0,然后这会对它+1,再右移一位,就会变成全0了。
 */
int fitsBits(int x, int n) {
  return !(((x>>(n+(~0)))+1)>>1);
}

/* 
 * divpwr2 - Compute x/(2^n), for 0 <= n <= 30
 *  Round toward zero
 *   Examples: divpwr2(15,1) = 7, divpwr2(-33,4) = -2
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 15
 *   Rating: 2 
 补码除以2的n次。也不是简单的直接右移,因为有符号数,正数除法是向下取整,而负数除法是向上取整。但是移位运算都是向下取整。

所以需要给负数加入一个偏差(1<<n)-1,判断负数就是看最高位是否为1
 */
int divpwr2(int x, int n) {
  return (x+(((x>>31)&1)<<n)+(~0)+(!((x>>31)&1)))>>n;
}


/* 
 * isLessOrEqual - if x <= y  then return 1, else return 0 
 *   Example: isLessOrEqual(4,5) = 1.
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 24
 *   Rating: 3
 正确的姿势应该是分类讨论两个数字的符号,如果符号不同,必然y最高位是0,x最高位是1
如果符号相同,直接做减法也不会溢出。
 */
int isLessOrEqual(int x, int y) {
  int signx = (x>>31)&1;
  int signy = (y>>31)&1;
  int signdif = (!signy)&signx;
  int signsam = (!(signx^signy))&(((x+(~y))>>31)&1);
  return signdif|signsam;
}


/*
这题是本场第二interesting的题吧。log2(x)就是x二进制里最高位的1在哪里。
想法也是类似分治,首先log2(x)=16×a+8×b+4×c+2×d+1×e,abcde都是0或者1,有这样的公式。
上面的代码就是求这个公式的实现。右移16位,判是否为0,就是a的值
知道了a之后,右移(8+16×a)位,判断是否为0,得到b的值。
以此类推。
*/
int ilog2(int x) {
  int ans = 0;
  ans = ans + ((!!(x>>(16 + ans)))<<4);
  ans = ans + ((!!(x>>(8 + ans)))<<3);
  ans = ans + ((!!(x>>(4 + ans)))<<2);
  ans = ans + ((!!(x>>(2 + ans)))<<1);
  ans = ans + ((!!(x>>(1 + ans)))<<0);

  return ans;
}

位运算十分有趣,感觉很需要大家的逻辑能力以及细致程度(例如到底移位31位还是30位?可惜我永远要算半天,希望大家可以秒得答案💡)。后面部分是我整理的一些很精妙的函数,可以看看,应该会有所启发~

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值