CS:APP DataLab详解

CS:APP DataLab实验详解

CS:APP DataLab实验详解

2024/10/13

实验规则

/* 
 * CS:APP Data Lab 
 * 
 * bits.c - Source file with your solutions to the Lab.
 *          This is the file you will hand in to your instructor.
 *
 * WARNING: Do not include the <stdio.h> header; it confuses the dlc
 * compiler. You can still use printf for debugging without including
 * <stdio.h>, although you might get a compiler warning. In general,
 * it's not good practice to ignore compiler warnings, but in this
 * case it's OK.  
 */

#if 0
/*
 * Instructions to Students:
 *
 * STEP 1: Read the following instructions carefully.
 */

You will provide your solution to the Data Lab by
editing the collection of functions in this source file.

INTEGER CODING RULES:
 
  Replace the "return" statement in each function with one
  or more lines of C code that implements the function. Your code 
  must conform to the following style:
 
  int Funct(arg1, arg2, ...) {
      /* brief description of how your implementation works */
      int var1 = Expr1;
      ...
      int varM = ExprM;

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

  Each "Expr" is an expression using ONLY the following:
  1. Integer constants 0 through 255 (0xFF), inclusive. You are
      not allowed to use big constants such as 0xffffffff.
  2. Function arguments and local variables (no global variables).
  3. Unary integer operations ! ~
  4. Binary integer operations & ^ | + << >>
    
  Some of the problems restrict the set of allowed operators even further.
  Each "Expr" may consist of multiple operators. You are not restricted to
  one operator per line.

  You are expressly forbidden to:
  1. Use any control constructs such as if, do, while, for, switch, etc.
  2. Define or use any macros.
  3. Define any additional functions in this file.
  4. Call any functions.
  5. Use any other operations, such as &&, ||, -, or ?:
  6. Use any form of casting.
  7. Use any data type other than int.  This implies that you
     cannot use arrays, structs, or unions.

 
  You may assume that your machine:
  1. Uses 2s complement, 32-bit representations of integers.
  2. Performs right shifts arithmetically.
  3. Has unpredictable behavior when shifting if the shift amount
     is less than 0 or greater than 31.


EXAMPLES OF ACCEPTABLE CODING STYLE:
  /*
   * 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;
  }

FLOATING POINT CODING RULES

For the problems that require you to implement floating-point operations,
the coding rules are less strict.  You are allowed to use looping and
conditional control.  You are allowed to use both ints and unsigneds.
You can use arbitrary integer and unsigned constants. You can use any arithmetic,
logical, or comparison operations on int or unsigned data.

You are expressly forbidden to:
  1. Define or use any macros.
  2. Define any additional functions in this file.
  3. Call any functions.
  4. Use any form of casting.
  5. Use any data type other than int or unsigned.  This means that you
     cannot use arrays, structs, or unions.
  6. Use any floating point data types, operations, or constants.


NOTES:
  1. Use the dlc (data lab checker) compiler (described in the handout) to 
     check the legality of your solutions.
  2. Each function has a maximum number of operations (integer, logical,
     or comparison) that you are allowed to use for your implementation
     of the function.  The max operator count is checked by dlc.
     Note that assignment ('=') is not counted; you may use as many of
     these as you want without penalty.
  3. Use the btest test harness to check your functions for correctness.
  4. Use the BDD checker to formally verify your functions
  5. 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.

/*
 * STEP 2: Modify the following functions according the coding rules.
 * 
 *   IMPORTANT. TO AVOID GRADING SURPRISES:
 *   1. Use the dlc compiler to check that your solutions conform
 *      to the coding rules.
 *   2. Use the BDD checker to formally verify that your solutions produce 
 *      the correct answers.
 */


#endif

实验过程

1. int bitXor(int x, int y)

实现异或运算。

考虑A^B = (~A&B) | (~B&A)

再用 ~& 替换 |

X|Y = ~(~X&~Y)

所以原式等于~(~(~A&B)&~(~B&A))

int bitXor(int x, int y) {
	return ~(~(~x&y)&~(~y&x));
}

2. int tmin(void)

返回int补码最小值。

32位补码的最小值即为100...0,也即1<<31

int tmin(void) {
	return 1<<31;
}

3. int isTmax(int x)

判断一个整数是否是补码最大值。

Tmax具有独特的性质~x = x+1,但-1也具有这个性质。

然而,~Tmax = Tmax+1 = 100..00 = -1, 但~(-1) = -1+1 = 0

所以判断条件即为~x == x+1 && !!(~x)(使用!!可把整数映射为布尔数)。

最后,由于&&两侧都为布尔数,故可以用&代替&&

由于a=b当且仅当a^b=0,所以用!^可以替换==

int isTmax(int x) { 
	return !((~x)^(x+1)) & !!(~x); 
}

4. int allOddBits(int x)

判断 x 的奇数位是否全部 1。

注意位的序号是从0开始计的。

构造掩码mask = 1010...10,那么奇数位均为1的x应当满足x & mask == model

与第3题同理,用!^代替==即可。

int allOddBits(int x) {  
	int tmp, mask; //注意C89的规则,变量要统一先声明
	tmp = 0xAA + (0xAA << 8);
	mask = tmp + (tmp << 16); 
	return !((x&mask)^mask);
}

5. int negate(int x)

x 进行求负操作。

x的相反数等于~x+1

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

6.int isAsciiDigit(int x)

判断 x 是否在0x30 ~ 0x39 之间。

0x30 = 0b110000, 0x39 = 0b111001

观察到满足条件的x的第5,6位(位从1记)一定是1且x没有更高的位,并且我们要取x的后四位进行判断。

考虑异或运算:与1异或取反,与0异或不变,这正好符合我们的要求。

mask = 0b111111,记a = x ^ mask

x满足要求,那么a应该没有第4位及以后更高的位,且只有以下几种取值情况。

xa
1100001111
1100011110
1100101101
1100111100
1101001011
1101011010
1101101001
1101111000
1110000111
1110010110

条件1.!(a>>4)a没有第4位及以后更高的位;

条件2.a>>3 | (a+1)>>3 | (a+2)>>3:所有的满足条件的x对应的a的首位都是1 或者 加1或2后首位为1。

int isAsciiDigit(int x) {
	int a = x^0x3F;   //a = x ^ 0b111111;
	return (!(a>>4)) & ( a>>3 | (a+1)>>3 | (a+2)>>3 ); 
}

7. int conditional(int x, int y, int z)

模拟三元运算符? :

思路:假定返回表达式的形式为val & y + ~val & z,若x不为0则val为全1,若x为0则val为全0。

先用!!x转为0或1的布尔值,再用f(x): x<<31>>31{0,1}映射到{00...0,11...1}

int conditional(int x, int y, int z) {
	int val = (!!x)<<31>>31;
	return (val & y) + (~val & z);
}

注1. 另一种把{0,1}映射到{00...0,11...1}的方法:利用1的补码是-1(11111111),0的补码是0,即利用f(x): ~x+1

注2. 可以用|代替+

8. int isLessOrEqual(int x, int y)

判断是否x<=y

两种情况:

  1. xy同号y>=x,则y-x>=0y-x的首位应为0。
  2. xy的符号不同y-x会产生溢出,数学上的y-x>=0当且仅当y为正,x为负。
int isLessOrEqual(int x, int y) {
	int flag1, flag2;
	flag1 = (~(y>>31)^(x>>31)) & !((y+(~x+1))>>31&1);	//x,y同号且y-x首位为0
	flag2 = (~y>>31&1) & (x>>31&1);	//x负y正
	return flag1 | flag2;
}

9. int logicalNeg(int x)

实现逻辑非!操作,即x为0则返回1,x非0则返回0。

除了0之外的数,相反数的最高位和该数的最高位二者至少有一个是1,

记表达式 a = (x|(~x+1))>>31

对于x = 0, a = 0; 对于x != 0, a = 11111111 = -1

因此返回a + 1即可满足条件

int logicalNeg(int x) {
	return ((x|(~x+1))>>31) + 1;
}

10. int howManyBits(int x)

返回表示整数x需要的最小位数。

对于正数,即求使x>>k=0(全0)的最小k再加1(正数的位模式首位必须为0);

对于负数,即求使x>>k=-1(全1)的最小k再加1(负数的位模式首位必须为1)。

我们可以把负数的情况化归为正数的情况,即若x为负数,howManyBits(x)=howManyBits(~x)

考虑按位异或运算:与0异或仍为本身,与1异或则取反,即x^0=xx^-1=~x,通过与x>>31按位异或可以实现把x统一“变为正数” 。

而要求k,我们可以用“二分”的想法,不断二分,可以画出决策树来理解。

int howManyBits(int x) {
	int mask, k, disp, curHigh16BitsNonZero, curHigh8BitsNonZero, curHigh4BitsNonZero, curHigh2BitsNonZero, curHigh1BitNonZero;

	mask = x>>31; //若x为负数,mask=全1;若x为正数,mask=全0
	x = x ^ mask; //若x为负数则取反;若x为正数则不变
  	
	/*
	 * 要求k,我们可以用“二分”的想法,不断二分,可以画出决策树来理解
	 */
	k = 0;
	curHigh16BitsNonZero = !!(x>>16);
	disp = curHigh16BitsNonZero << 4; //位移量,若k>16,disp=16;否则disp=0
	k = k + disp;  //若k>16,k先加16;否则k不变
	x = x >> disp;   //“框住”x的“未被确认”的那16位

	//后面同理
	curHigh8BitsNonZero = !!(x>>8);
	disp = curHigh8BitsNonZero << 3;
	k = k + disp;
	x = x >> disp;

	curHigh4BitsNonZero = !!(x>>4);
	disp = curHigh4BitsNonZero << 2;
	k = k + disp;
	x = x >> disp;

	curHigh2BitsNonZero = !!(x>>2);
	disp = curHigh2BitsNonZero << 1;
	k = k + disp;
	x = x >> disp;

 	curHigh1BitNonZero = !!(x>>1);
	disp = curHigh1BitNonZero;
	k = k + disp;
	x = x >> disp;

	k = k + x;    
	//容易被遗忘的一步:若最后还剩的x是1,这个1也需要被计入(即curBitNonZero)

	return k+1;
}

11. unsigned floatScale2(unsigned uf)

求以unsigned形式表示的浮点数 uf *2 之后的位表示。

解析出uffloat表示的各个部分,并针对三种类型分别进行判断、运算。

NaN或无穷大:返回原本的数。

非结构化的数:直接把expfrac部分的总体左移1位,就可以实现uf*2。(也许它会变为结构化的数,非结构化数到结构化数是可以平滑过渡的。)

结构化的数:只需要E+=1即可实现乘以2。

unsigned floatScale2(unsigned uf) {
	// 我们要把uf解析出float变量的各个部分
	int s, exp, frac;
	s = uf>>31&1;
	exp = uf>>23&0xff;
	frac = uf&0x7fffff;
	// 针对三种类型进行判断
	// NaN或无穷大
	if(exp == 0xff){
		return uf;
	}
	//非规格化的数,表示0或接近0的数
	else if(exp == 0){
		// 可以直接把exp和frac部分的总体左移1位,就可以实现uf*2,也许它会变为结构化的数
		return s<<31 | uf<<1;
	}
	// 结构化的数
	else{
		// 只需要E+1即可实现乘以2
   		return s<<31 | (exp+1)<<23 | frac;
	}
}

12. int floatFloat2Int(unsigned uf)

求以unsigned形式表示的浮点数 uf 强制转换为整数后的位表示。

仍拆解uf,大方向上分三类情况。

非规格化数:其绝对值小于 1,根据整数截断规则,结果直接返回 0。

inf和NaN:一定会超出int的表示范围,返回特殊值 0x80000000 作为溢出表示。

规格化数

  • 通过指数偏移量 E = exp - 127 计算浮点数的实际指数。
  • 如果 E 超出 32 位整数能表示的范围,即E>31,返回溢出值 0x80000000
  • 如果 E < 0,表示绝对值小于 1,结果也为 0。
  • 如果0 <= E <= 31,计算出对应的M并移动相对应的小数点。
int floatFloat2Int(unsigned uf) {
  int s, exp, frac, E, M;
  // 拆解
  s = uf>>31&1;
  exp = uf>>23&0xff;
  frac = uf&0x7fffff;
  // 非规格化的数:绝对值小于1,根据截断规则,返回0
  if(exp == 0){
    return 0;
  }
  // inf和NaN:一定会超出int的表示范围
  else if(exp == 255){
    return 0x80000000u;
  }
  // 规格化的数
  else{
    // 计算规格化的数的E
    E = exp - 127;
    // 超出范围
    if(E > 31){
      return 0x80000000u;
    }
    // E<0能确定|f|<1,这是因为2^E<=0.5,而M<2,故|f|<1
    else if(E < 0){
      return 0;
    }
    else{
      /* 
       * 注意float向int强制转换并不是直接简单地除去frac部分
       * M = 1.(frac), |V| = M * 2^E
       * 理解的关键:M乘以2^E等价于M的小数点右移E位
       * 我们要判断uf的小数点在哪个位置,也就是M*2^E的小数点在哪个位置
       */
      M = 1<<23 | frac; //这里的M实际上是M*2^23
      if(E >= 23){
        M = M << (E-23);  //把小数点少往右移的移过去
      }else{
        M = M >> (23-E);  //把小数点多往右移的移回来
      }
      // 考虑符号位
      if(s){
        M = ~M + 1; //取相反数
      }
      return M;
    }
  }
}

13. unsigned floatPower2(int x)

计算浮点数 2^x的位表示。

关注float各个分类所能表达的正数V (s=1)的范围。 V = M * 2^E

  1. 规格化的数 1 <= exp <= 254

M = 1.(frac), E = exp - 127, exp∈[1, 254];

M∈[1, 2), E∈[-126, 127];

​ 故V∈[2^{-126}, 2^128)

  1. 非规格化的数 exp = 0

​ 只考虑frac不全为0时的情况(即正数的情况)

M!=0时,有M∈[2^{-23}, 1),

​ 故V∈[2^{-149}, 2^{-126})

  1. **NaN+inf ** exp = 255

判断2^x是哪一类数,并根据那类数的规则返回对应的值。

unsigned floatPower2(int x) {
  int exp, frac;
  // 极小的数
  if(x < -149){
    exp = 0;
    frac = 0;
  }
  // 非规格化的数 V = frac * 2^{-126} = 2^x  =>  frac = 2^(x + 126) = 1 * 2^(x + 126)
  else if(x < -126){
    exp = 0;
    frac = 1 << (x + 126 + 23); //还要左移23是因为保证frac是小数部分的解释方式
  }
  // 规格化的数 V = M * 2^{exp - 127} = 2^x  => x由exp贡献,M = 1,即frac = 0,exp = x + 127
  else if(x < 128){
    frac = 0;
    exp = x + 127;
  }
  // inf 
  else{
    exp = 0xff;
    frac = 0;
  }
   
  return (exp<<23) | frac;
}

技巧总结

  1. !!x可以将整数x映射到{0,1}

  2. a=b当且仅当a^b=0,故a==b等价于!(a^b)

  3. f(x): ~x+1可以把{0,1}映射到{00...0,11...1}

  4. 取a的符号位:a>>31&1

  5. 按位异或运算的两种理解与应用方式:

    1. 相同为0,不同为1,即x^x = 0x^~x = 11...1 = -1
    2. 与0异或仍为本身,与1异或则取反,即x^0 = xx^-1 = ~x
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值