DataLab

61 阅读8分钟

实验说明

首先下载好实验源码,github甚至有部署作业的shell脚本可以使用。datalab目录结构如下。

image.png

同时确保你仔细阅读两个文档

./README-datalab    #介绍作业内容
./datalab-handout/README # 测试工具和辅助工具文档

btest

btest是测试bits.c作业的程序,检查你的代码是否能够通过测试用例。

# 编译btest文件, 生成测试程序
make btest

使用方式如下

 ./btest -h Usage: ./btest [-hg] [-r ] [-f [-1|-2|-3 ]*] [-T ] -1 Specify first function argument -2 Specify second function argument -3 Specify third function argument -f Test only the named function -g Format output for autograding with no error messages -h Print this message -r Give uniform weight of n for all problems -T Set timeout limit to lim

helper

实验还提供了2个文件,fshow和ishow,可以输入整数,让程序帮你将整型和浮点数表示出来。

unix> ./ishow 0x27
Hex = 0x00000027,   Signed = 39,    Unsigned = 39
​
unix> ./ishow 27
Hex = 0x0000001b,   Signed = 27,    Unsigned = 27
​
unix> ./fshow 0x15213243
Floating point value 3.255334057e-26
Bit Representation 0x15213243, sign = 0, exponent = 0x2a, fraction = 0x213243
Normalized.  +1.2593463659 X 2^(-85)
​
linux> ./fshow 15213243
Floating point value 2.131829405e-38
Bit Representation 0x00e822bb, sign = 0, exponent = 0x01, fraction = 0x6822bb
Normalized.  +1.8135598898 X 2^(-126)

本次实验需要自己实现的文件

bits.c

动手写实验

  • 本次实验只能使用下面的运算符
! ˜ & ˆ | + << >>
  • 使用的常量不超过 8 bits

部分题目不太好写,还是想了很久都没解决,参考了其他博客实现思路。

bitXor

异或可以拆为2个部分,离散数学或者数字电路中都有介绍(德摩根律)。

int bitXor(int x, int y)
{
  // 先不要强制用 ~和&直接写,可以用|等其他的赋值,再拆解
  /* 
      1. ~(x & y) & (x | y)
      2. ~(x & y) & ~(~x & ~y)  这一步类似于集合操作对 Union 取反(德摩根律)
   */
  return ~(x & y) & ~(~x & ~y);
}

tmin

直接左移31位得到

int tmin(void)
{
  // 1逻辑左移31位, 100...0
  return 1 << 31; 
}

isTmax

Tmax判断,使用Tmin和Tmax的关系来实现。

int isTmax(int x)
{
  /* 
    不能使用<<操作, 那就利用Tmin和Tmax的关系
    -Tmin = ~Tmin + 1 = Tmin
    Tmax = ~Tmin
   */
    // int xplus = x + 1; // if tmax, then x+1 equals tmin
    // int y = ~(x ^ xplus); // if x is tmax, then tmin ^ tmax equals tmin
    // return !(y+!xplus); // special case: 0
​
    return !(~(x^(x+1))+!(x+1)); // 7 operators
}

allOddBits

这个函数就是实现判断输入x是否它的奇数位全置为1。如果x奇数位全为1一定满足如下表达式。

x=1x301x28...1x0, xi0,1, i=2k(1k15)x = 1x_{30}1x_{28}...1x_{0}, \ x_{i} \in {0,1},\ i = 2k(1\le k \le 15)

注释中也给出了提示,最典型的allOddBits就是0xAAA...A,可以采用0xAAA...A位作为掩码进行与运算和异或运算判断输入的x是否为全奇数位。

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

negate

最简单的题目,课堂上还讲过,two's complement取反+1(另外,Tmin这样做还是Tmin自己)

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

isAsciiDigit

通过最高位和位移运算判断输入是否在范围内。

  1. 如果超过0x39,相加后最高位进位,通过右移判断最高位是否大于上界
  2. 如果小于0x30,取相反数然后相加,如果最高位还是1,说明小于0x30,再右判断是否小于下界
int isAsciiDigit(int x)
{
  int mostSignificant = 1<<31; // 记录负号
  int upperBound = ~(mostSignificant|0x39); 
  int lowerBound = ~0x30 + 1;
  // 如果不在范围内, 最高位就是1
  upperBound = mostSignificant&(upperBound+x)>>31; 
  lowerBound = mostSignificant&(lowerBound+x)>>31;
  return !(upperBound | lowerBound);
}

conditional

使用位运算来实现三目运算符。

思路:x为判断条件,那么肯定要先转换成01,采用逻辑非将其变为0和1。通过掩码形式返回结果,1对应全1,0则为全0,可以用补码来实现。

int conditional(int x, int y, int z)
{
  x = !!x; // convert to 0 or 1
  x = ~x+1; // convert to mask
  return (x&y)|(~x&z);
}

isLessOrEqual

通过符号位和数字做差判断大小。

  • 符号判断:如果x为正,y为负,直接放回0,0&其他值都是0;如果x为负,y为正返回1。
  • 如果x, y 符号相同,判断x-y<=0成立,
/*
 * 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 signx = (x>>31)&1;
  int signy = (y>>31)&1;
​
// 比大小, 看最高位
  int sub = x+(~y+1);
  int le = !((sub&(1<<31)) ^ (1<<31)) | // 取出符号位, 如果为负数, 这一部分为1
            !(sub^0);  // 不为0 这一部分为0
​
  return (!(~signx & signy)) &  // 符号判断
          ((signx & ~signy)|le); // 相同符号大小判断
}

logicalNegate

考虑2个特殊的值即可,0和Tmin,除了这2个数的相反数是本身,其他都是不同的补码。但是0的符号位和补码进行或运算后是0,而Tmin不同。Tmin符号位和Tmin补码进行或运算后是0xffff。其他值操作后也都是0xffff。

/*
 * 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 ((x|(~x+1))>>31)+1;
}

howManyBits

计算描述输入值x需要的最小位数,对于同一个值,可能有多个不同位数表示,-5=1011=11011=111011。负数的补码前面有多个1,但是最终结果都一样。对于正数找最高位1,负数找最高位0。

取出高位后就对剩下的数据位进行二分法判断是否需要高位。

/* 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 sign = x>>31;
  x = (sign&~x) | (~sign&x); // 找到x的最高位1
  
  // 二分法拆分bits
  /* 
    比如 0100 1100 1100 1100 1100 1100
    1. 高16位中有 0100 1100, 那只要关注高16位, x移动16位. +16
    2. 再取高16位的高8位, 高8位为0, 没有内容, x移动0位. +0
    3. 看当前0100 1100的高4位,x移动后有0100,那就看0100,x移动4位. +4
    4. 再看0100的高2位, x移动后变为01, x移动2位. +2
    5. 01再移动1位, 为0. x移动1位. +1
    6. 最后符号位+1
   */
​
  int b16 = (!!(x>>16)) << 4;
  x = x>>b16;
​
  int b8 = (!!(x>>8)) << 3;
  x = x>>b8;
​
  int b4 = (!!(x>>4)) << 2;
  x = x>>b4;
​
  int b2 = (!!(x>>2)) << 1;
  x = x>>b2;
​
// 最后一步这里可能不太好理解, 这里是把最后一个有效位1给移出去了,然后变为了0
  int b1 = (!!(x>>1)); 
  int b0 = x >> b1;
​
  return b16+b8+b4+b2+b1+b0+1;
}

floatScale2

输入一个unsigned int数据,它表示的是一个单精度浮点数,先将其double后按照unsigned返回。但是传进来的uf需要进行下面的判断

  • uf为非规格化数据,exp=0,此时M<1,进行左移
  • uf为规格化数据,exp≠0,将exp+1即可,还要考虑溢出问题,浮点数溢出就是无穷(infinity)

参考CSAPP教材 Floating Poing 2.4章节来看,这道题肯定没问题。

/*
 * 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)
{
  int exp = (uf&0x7f800000)>>23;
  if(exp==0xff) return uf; // NaN or Infinity
​
  unsigned sign = uf & (1<<31);
​
  // denormalized
  if(exp==0) return uf<<1|sign;
​
  // overflow
  if(++exp==0xff) return sign|0x7f800000;
​
  // normalized
  return exp<<23 | (uf&0x807fffff);
}

floatFloat2Int

将单精度浮点数转换为整型,分类讨论

  • E<0,浮点数为非规格化或者是1.xxx乘2的-n次幂,这种情况小于0,返回0

  • E>31,超过int能够表达的最大值,按照注释返回0x80000000

  • 0<E<31

    • 23<E<31,frac部分全都在小数点左边,左移
    • 0<=E<=23,frac需要右移
int floatFloat2Int(unsigned uf)
{
  unsigned exp = (uf & 0x7f800000)>>23;
  int E = exp - 127;
  int frac = (uf & 0x7fffff) | 0x800000;
​
  if(E<0) return 0; // 小于1的返回0
  if(E>31) return 0x80000000; // 超过int最大// 移动小数点
  if(E>23) {
    frac <<= (E-23);
  } else {
    frac >>= (23-E);
  }
​
  if(!((uf>>31) ^ (frac>>31))) { // 左移符号位不变
    return frac; 
  }
​
  // 符号位变为负, 溢出
  if(frac>>31) {
    return 0x80000000;
  }
​
  // 取补码
  return ~frac + 1;
}

floatPower2

输入一个x,返回

2x2^x

返回值是单精度的unsigned,如果值太小返回0,如果太大返回Infinity。首先frac字段是全为0的,因为是2的n次方。这里的x实际上就是指数E,按照下面的式子计算即可。

x=E, E=expbiasx = E,\ E = exp - bias
/*
 * 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)
{
  if(x>127) return 0x7f800000; // return Infinity
  if(x<-126) return 0;
​
  return ((x+127)<<23); // x = E = exp -bias, exp = x + bias
}

image.png