Lab1 Data Lab

Lab 1 Data Lab

写在前言:这个实验的来源是CSAPP官网:CSAPP Labs ,如果感兴趣的话,可以点击这个链接🔗去下载,Data Lab这个实验可以检验我们对数据的编码以及IEEE754浮点数格式的掌握程度,只有我们充分掌握了CSAPP书上第二章的内容,对于这个实验,可以说是游刃有余了,Data Lab包含整数和浮点数编码两部分,相应的会做出一些限制,主要任务就是让我们用位级操作实现一些功能。

编码限制

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.

bitXor

函数的声明和限制如下:

//1
/* 
 * bitXor - x^y using only ~ and & 
 *   Example: bitXor(4, 5) = 1
 *   Legal ops: ~ &
 *   Max ops: 14
 *   Rating: 1
 */
int bitXor(int x, int y) {
	return 0;
}

这道题目限制使用两个操作符:按位取反~、按位与&,根据离散数学的基本等值式:异或可以表示为:

x ^ y <=> (x & ~y) | (~x & y) <=> ~(~(x & ~y) & ~(~x & y))

因此答案是:

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

tmin

函数声明如下:

/* 
 * tmin - return minimum two's complement integer 
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 4
 *   Rating: 1
 */
int tmin(void) {
    return 0;
}

要求我们得到32位补码整数的最小值,就是0x80000000,可以使用一个简单的移位实现

答案为:

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

isTmax

函数声明如下:

//2
/*
 * 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) {
    return 0;
}

要求我们判断 x 是否为32位补码整数最大值0x7fffffff,一种思路是先生成Tmax32,然后在使用异或 ^ 和逻辑非 ! 操作符判断 x 是否是 Tmax32

int isTmax(int x) {
    int tmax = (1 << 31) - 1;
    return !(x ^ tmax);
}

如果 x=Tmax,那么表达式 !(x ^ tmax)=1,因为 a^a=0,两个相同的数进行异或一定为0。

但是题目要求这里不能使用减法和移位操作,所以要另辟蹊径。

对于 Tmax,有一个性质:(Tmax+1) + (Tmax+1)=(0x7fffffff+1)+(0x7fffffff+1)=0x80000000+0x80000000=0,意味着我们可以考虑计算表达式 (x+1)+(x+1) 判断它是否为0就可以了,但是这个性质对于x=-1也是满足的,所以还需要排除这个情况:

int isTmax(int x) {
    int expr = (x + 1) + (x + 1);
    // x is Tmax if expr = 0 and x != -1
    int istmax = !expr & !!(x + 1);
    return istmax;
}

allOddBits

函数声明如下:

/* 
 * 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) {
    return 0;
}

要求我们判断x的奇数位是否全部设置为了1,因为只需要考虑奇数位,可以先生成一个奇数位掩码0xAAAAAAAA,位级整数编码要求我们只能使用0-0xff 这种小整数,所以不能直接使用这种大整数,可以通过0xAA移位合并起来,然后生成掩码后,按位与 x,将结果和这个掩码判断,如果相等,那么所有奇数位为1:

int allOddBits(int x) {
	int mask = (0xAA << 24) | (0xAA << 16) | (0xAA << 8) | 0xAA;
    int result = !((mask & x) ^ mask);
    return result;
}

negate

函数声明如下:

/* 
 * negate - return -x 
 *   Example: negate(1) = -1.
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 5
 *   Rating: 2
 */
int negate(int x) {
    return 0;
}

要求计算表达式 -x,但不使用减号,很简单,根据位级整数的性质:-x=~x+1

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

isAsciiDigit

函数声明如下:

//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
 */
int isAsciiDigit(int x) {
    return 0;
}

这个函数要求我们判断输入x是否为Ascii的数字,就是判断数字x是否在这个范围内:0x30<=x<=0x39,分析如下:

  • 0x30=0011 0000 并且 0x39=0011 1001,Ascii数字的编码,形式为0x3.,第5,6比特均为1,高位比特均为0,因此可以利用这一特性,排除一些选项,可生成掩码 mask=0xffffff00
  • 在第一个条件后,范围变成了0x30 --- 0x3f,显然有一些是不满足的,因为Ascii数字编码最大值为 0x39 所以我们可以计算表达式 x+6,如果大于了0x3f,那么x不是Ascii数字,这时可以使用1的掩码mask判断表达式x+6的结果第5,6比特是否都为1
int isAsciiDigit(int x) {
    // Mask = 0xffffff00
    int mask = ~0 + (~0xffff + 1);
    int flag1 = !((x & mask) ^ 0x30);
    int flag1 = !(((x + 6) & mask) ^ 0x30);
    return flag1 & flag2;
}

conditional

函数声明如下:

/* 
 * conditional - same as x ? y : z 
 *   Example: conditional(2,4,5) = 4
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 16
 *   Rating: 3
 */
int conditional(int x, int y, int z) {
    return 0;
}

这个函数要求我们使用位级操作实现三目运算符,这个类似于一个多路复用器 Multiplex,根据 x 的值选择y和z,如果x不为0,结果为y,否则为z。可利用x生成一个掩码:mask=(!x << 31) >>31,然后利用按位或|选择结果。

int conditional(int x, int y, int z) {
    // mask = 0xfffffff if x != 0 else mask = 0
    int mask = (!x << 31) >> 31;
    int result = (~mask & y) | (mask & z);
}
WE
not
and
and
y
z
or
answer

WE就是多路复用器的写使能,用来判断选择哪一个输入,和这里的三目运算符原理是一样的。

isLessOrEqual

函数声明如下:

/* 
 * 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) {
    return 0;
}

这个函数要求实现一个运算符 <=,我们可以考虑计算表达式 x-y,判断它是否大于0,从而得出x和y的大小关系,前提是x-y不能溢出,可以分以下3个情况:

  • x>0并且y<0,返回1
  • x<0并且y>0,返回0
  • x>0, y>0或者x<0, y<0
    • 计算表达式 x-y
    • 判断与0的关系
int isLessOrEqual(int x, int y) {
  // Get xsig and ysig
  int xsig = (x >> 31) & 1;
  int ysig = (y >> 31) & 1;
  // Compute x - y
  int z = x + ~y + 1;
  int lflag = (z >> 31) & 1;
  // 相等标记
  int zflag = !(x ^ y);
  // result=1 if x>0 且 y<0 或者 x和y同号且小于标记或零标记为1
  int result = (xsig & (~ysig & 1)) | (!(xsig ^ ysig) & (lflag | zflag));
  return result;
}

logicalNegate

函数声明如下:

/* 
 * 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 
 */
int logicalNeg(int x) {
	return 0;
}

要求实现逻辑非运算符,可以利用以下性质:

  • x==~x+1,只有当 x为0或者 Tmin32=0x80000000
  • 计算相反数表达式 ~x+1,排除x=0x80000000这种情况。
    • 可以通过它们的符号位进行判断
int logicalNeg(int x) {
  int y = (~x + 1);
  // 只有当 x 和 y=-x 的符号位均为0 结果才为1
  int result = ((~x & ~y) >> 31) & 1;
  return result;
   
}

howManyBits

函数声明如下:

/* 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) {
    return 0;
}

这个函数要求我们获取能够以二进制补码表示 x 的最小位长,这里涉及到一个截断的问题,

假设x表示模式为:
x = [ x 31 , x 30 , . . . , x 1 , x 0 ] x=[x_{31},x_{30},...,x_1,x_0] x=[x31,x30,...,x1,x0]
假设x截断为n位二进制数:
x ′ = [ x n − 1 , x n − 2 , . . . , x 1 , x 0 ] x^{'}=[x_{n-1}, x_{n-2},...,x_1,x_0] x=[xn1,xn2,...,x1,x0]
如果两个数相等,那么一定有:
x 32 = x 31 = ⋯ = x n = x n − 1 x_{32}=x_{31}=\cdots=x_n=x_{n-1} x32=x31==xn=xn1
所以我们可以有这种思路:

  • 如果 (x << n) >> n == x,那么它可以被 32-n 位的二进制补码数精确表示。
  • 由于我们不能使用循环迭代的方式去迭代n,找到临界的n就行了,要另辟蹊径。
  • 考虑使用二分查找
    • 第一次检查n=16
    • 第二次检查n=8
    • 依次类推
    • 直到n=1为止
int howManyBits(int x) {
      // Use binary search
      int tag16 = 0;
      int tag8 = 0;
      int tag4 = 0;
      int tag2 = 0;
      int tag1 = 0;
      int y = 0;

      // if tag16 = 1, it means that it can be represented by less 16 bit.
      y = (x << 16) >> 16;
      tag16 = !(x ^ y);
      x = x << (tag16 << 4);

      y = (x << 8) >> 8;
      tag8 = !(x ^ y);
      x = x << (tag8 << 3);

      y = (x << 4) >> 4;
      tag4 = !(x ^ y);
      x = x << (tag4 << 2);

      y = (x << 2) >> 2;
      tag2 = !(x ^ y);
      x = x << (tag2 << 1);

      y = (x << 1) >> 1;
      tag1 = !(x ^ y);

      // result = 32 - (tag16 * 16 + tag8 * 8 + tag4 * 4 + tag2 * 2 + tag1)
      return 32 + ~((tag16 << 4) + (tag8 << 3) + (tag4 << 2) + (tag2 << 1) + tag1) + 1;
}

实际上上面就是将一个二分查找的一个循环结果使用循环展开的方式,至于为什么使用二分查找,是因为题目限制了操作符个数,否则可以穷举n=1至n=32,不过这样太复杂且操作符太多。

floatScale2

函数声明如下:

//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
 */
unsigned floatScale2(unsigned uf) {
    return 2;
}

浮点编码要求我们使用unsigned为类型的表示的浮点数进行一些操作,我们必须掌握IEEE754浮点数格式才能够在位级上使用unsigned进行浮点操作,要求比较高。

这个函数要求我们实现的就是 2 * uf 这个表达式,我们必须要考虑以下情况:

  • Case 1:当 uf 是NaN或者Inf,那么返回 uf
  • Case 2:当 uf 的阶码 exp=254, 那么 2 * uf = INF
  • Case 3:如果 uf 的阶码 0 < exp < 254,那么只需要将 uf 的阶码+1更新其阶码域即可
  • Case 4:如果 uf 为阶码 exp=0,也就是 uf 为非规格化数
    • 不能简单地让阶码+1,因为非规格化数表示为 V = ( − 1 ) s × 0. f × 2 1 − b i a s , b i a s = 127 V=(-1)^s \times 0.f\times2^{1-bias},bias=127 V=(1)s×0.f×21bias,bias=127
    • 只需要将 frac左移一位即可,但是要考虑如下情况:
      • 因为frac是23位的,如果frac的第23比特为1,那么就不能左移,因为会超出范围,这时应该将阶码+1,变成一个规格化数
      • 否则,直接frac左移一位更新frac域即可
unsigned floatScale2(unsigned uf) {
      // Extract s
      unsigned s = uf & 0x80000000;
      // Extract exp
      int exp = (uf >> 23) & 0xff;
      // Extract frac
      int frac = 0x7fffff & uf;
      unsigned result = 0;

      if (exp == 255) {
        // NaN or infinity
        result = uf;
      } else if (exp == 254) {
        // 2 * f == inf
        exp = 255;
        frac = 0;
        result = s | (exp << 23) | frac;
      } else if (0 < exp && exp < 254) {
        // 2 * f can be represented as normalized number
        exp += 1;
        result = s | (exp << 23) | frac;
      } else {
        // exp == 0, 2 * f can be represented as denormalized number or a normalized number.
        if (frac < 0x800000) {
            frac <<= 1;
            result = s | (exp << 23) | frac;

        } else {
            // 2 * f can't be represented as a denormalized number.
            exp = 1;
            result = s | (exp << 23) | frac;	
        }
      }

      return result;
}

floatFloat2Int

函数声明如下:

/* 
 * 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 floatFloat2Int(unsigned uf) {
    return 2;
}

这个函数要求我们实现一个从浮点数到整数的强制转换操作符 (int),但是浮点数强制转换为整数,有可能转换不精确,因为浮点数的精度有限以及整数int表示范围小于浮点数。

需要考虑下列不同情况:

  • uf 为 NaN或者Inf,即阶码位exp=255,返回 0x80000000u
  • uf 为 非规格化数,阶码位 exp=0,返回 0
    • 因为非规格化数的表示值为: V = ( − 1 ) s × 0. f × 2 1 − b i a s , b i a s = 127 V=(-1)^s \times 0.f\times2^{1-bias},bias=127 V=(1)s×0.f×21bias,bias=127,不管怎么舍入,都只能舍入到0
  • uf 为规格化数, V = ( − 1 ) s × 1. f × 2 e x p − b i a s , b i a s = 127 V=(-1)^s\times 1.f\times2^{exp-bias},bias=127 V=(1)s×1.f×2expbias,bias=127考虑以下情况
    • exp<bias,舍入到0
    • exp=bias,判断尾数frac的第23位是否为1,如果是,舍入到2,否则舍入到1。
    • exp>bias,需要判断是否超出范围
      • 此时转换为整数的结果为 result = 1frac << (exp - bias)
      • 因为1frac已经占据了24比特,如果移位量 exp-bias 大于等于8,那么就超出整数的表示范围了,Overflow
      • 否则,直接移位即可。
int floatFloat2Int(unsigned uf) {
  int bias = 127;
  int exp = (uf >> 23) & 0xff;
  int frac = uf & 0x7fffff;
  int s = uf & 0x80000000;
  int result = 0;
  int m = 0;
  
  if (exp == 255) {
  	// NaN or Infinity.
  	result = 0x80000000u;
  } else if (exp == 0) {
  	// Unormalized number(too small)
  	result = 0;
  } else if (exp < bias) {
  	// Normalized number but small to be rounded to zero
  	result = 0;
  } else if (exp == bias) {
  	// V = (-1)^s * 1.f
  	if (frac >= 0x400000) {
  		// round to even
  		result = 2;
  	} else {
  		result = 1;
  	}
  	result = s ? -result : result;
  } else if (exp > bias) {
  	if (exp - bias >= 8) {
  		// Overflow
  		result = 0x80000000u;
  	} else {
  		// 0 < exp - bias < 8
  		m = (1 << 23) | frac;
  		result = m << (exp - bias);
 			result = s ? -result : result;
  	}
  }
	
	return result;
}

floatPower2

函数声明如下:

/* 
 * 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
 */
unsigned floatPower2(int x) {
    return 2;
}

这个函数要求计算表达式 2 x 2^x 2x,看这个要求貌似很简单,但实际上没那么简单,要考虑比较多的因素,因为x作为浮点数的阶码域,它的范围是有限的,考虑以下情况:

  • 如果 x >= 128,结果太大,返回 +INF
  • 如果 -126 <= x <= 127,那么表示为规格化数
    • 计算 exp= x + 127,结果为:(exp << 23)
  • 如果 -149 <= x <= -127,表示为非规格化数,
    • 因为非规格化数的特殊形式,非规格化数最小时,frac=1
    • 最大时,frac的第23位为1,其余为0
    • 因此可以在规格化数的范围扩展23个数量级到达 2 − 149 2^{-149} 2149
    • 此时,计算 k=x+149
    • 得到尾数 frac = 1<<k
    • 此时frac就是结果
  • 如果 x<=-150,那么x太小了,返回0即可
unsigned floatPower2(int x) {
	unsigned bias = 127;
	unsigned exp = 0;
	unsigned result = 0;
	int k = 0;
	
	if (x >= 128) {
		// x is too big.
		return 0x7f800000u;
	} else if (-126 <= x && x <= 127) {
		// 2.0^x can be represented as normalized number.
		exp = x + bias;
		result = exp << 23;
		return result;
	} else if (-149 <= x && x <= -127) {
		// 2.0^x can be represented as denormalzied number.
		exp = 0;
		// Compute shift amount 0 <= k <= 22
		k = x + 149;
		result = 1 << k;
		return result;
	} else {
		// x is too small. x<=-150
		return 0u;
	}
}

总结

以上所有题目的方法均为自己所想,可能不是最简的,但是经过测试,这些解决方法都能通过测试。这个实验很有趣,建议自行完成,自己去思考有助于真正掌握数据的位级编码,比如二进制补码整数和无符号整数在运算过程中是具有相同的位级形式,区别仅仅是表示的不同;再如浮点数的IEEE754编码格式,分为4种情况:NaN、无穷大、规格化数、非规格化数等等,它们的编码格式不同,表示值的格式也不同。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值