课程主页:http://www.cs.cmu.edu/afs/cs/academic/class/15213-f15/www/schedule.html

课程资料:https://github.com/EugeneLiu/translationCSAPP

课程视频:https://www.bilibili.com/video/av31289365/

这一部分回顾CSAPP的Data Lab。

bitXor

$x$ $y$ $x\text{^}y$
1 1 0
1 0 1
0 1 1
0 0 0

不难看出

代码如下

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

tmin

代码如下

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

isTmax

首先给出如下结论,只有当且仅当

才成立

证明:

假设

那么

另一方面

所以

等价于(其中$k\in \mathbb Z$)

利用二进制数表示的唯一性可得

有了如下结论,可以先利用如下代码判断x是否为-1或$\text{Tmax}$:

//判断是否为xffffffff或Tmax,如果成立,则y3为0
int y1 = x + 1;
int y2 = ~x;
int y3 = (y1 ^ y2);

接着要排除-1的情形,这一点只要利用-1的表示为全1即可

//判断是否为0xffffffff,如果成立,则z2为1
//如果x为0xffffffff,则z1为0
int z1 = ~x;
int z2 = !z1;

最后进行简单的操作即可,完整的代码如下

int isTmax(int x) {
    //判断是否为xffffffff或Tmax,如果成立,则y3为0
    int y1 = x + 1;
    int y2 = ~x;
    int y3 = (y1 ^ y2);

    //判断是否为0xffffffff,如果成立,则z2为1
    //如果x为0xffffffff,则z1为0
    int z1 = ~x;
    int z2 = !z1;

    //生成结果,如果x为Tmax则r=False,所以要取非
    int r = (z2 | y3);
    r = !r;

    return r;
}

allOddBits

设置mask:0101,0101,…,0101,然后将x与其做异或运算,最后取非即可,代码如下

int allOddBits(int x) {
    //0101,0101
    int y1 = 170;
    //0101,0101,0101,0101
    int y2 = (y1 << 8) + y1;
    //0101,0101,0101,0101,0101,0101,0101,0101
    int y3 = (y2 << 16) + y2;
    //取交集
    int y4 = (x & y3);
    //判断是否相同
    int y5 = (y4 ^ y3);

    return !y5;
}

negate

利用第三讲的内容可知

代码如下

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

isAsciiDigit

分为两部分,第一部分判断4到7位(从右往左数)是否为0011,只要利用移位运算和异或运算即可:

int y1 = !((x >> 4) ^ 3);

第二部分判断0到3位,注意第0位是没有影响的,所以只要判断其他几位是否为1即可:

//判断末4位范围
//判断第3位是否为1   
int y2 = (x & 8) >> 3;
//判断第2位是否为1
int y3 = (x & 4) >> 2;
//判断第1位是否为1
int y4 = (x & 2) >> 1;

另外不难发现如果第3位为1,则1,2位应该必然为0;如果第3位为0,则其他位置可取任意值,所以初步的代码为

int z1 = y1 & (!y2);
int z2 = y1 & y2 & (!y3) & (!y4);
int z3 = z1 | z2;

但是这样会超过运算数限制,所以要对上述操作进行优化,首先不难看出y1可以提取出来,另一方面,

所以

最终得到如下代码

int z1 = (!y3) & (!y4);
int z2 = !y2;
int z3 = y1 & (z1 | z2);

完整的部分如下:

int isAsciiDigit(int x) {
    //范围为0011,0000到0011,1001
    //判断0011部分是否相同
    int y1 = !((x >> 4) ^ 3);
    //判断第3位是否为1
    int y2 = (x & 8) >> 3;
    //判断第2位是否为1
    int y3 = (x & 4) >> 2;
    //判断第1位是否为1
    int y4 = (x & 2) >> 1;
    //如下代码会超过运算数限制
    //int z1 = y1 & (!y2);
    //int z2 = y1 & y2 & (!y3) & (!y4);
    //int z3 = z1 | z2;
    int z1 = (!y3) & (!y4);
    int z2 = !y2;
    int z3 = y1 & (z1 | z2);

    return z3;
}

conditional

首先考虑一比特的情形,不难发现输出结果可以用如下操作完成:

多个比特的情形类似,只要构造mask即可:

int conditional(int x, int y, int z) {
  //声明变量
  int flag, notflag, a, b, c;
  //将x转换为0或1,x为0的时候转换为1,其余情形为0
  flag = (!x);
  //取反转换为1110,1111,x为0的时候转换为1110,其余情形为1111
  flag = ~flag;
  //加1转换为1111,0000,x为0的时候转换为1111,其余情形为0000
  flag = flag + 1;
  //取反
  notflag = ~flag;
  //生成结果
  a = notflag & y;
  b = flag & z;
  c = (a | b);

  return c;
}

isLessOrEqual

首先判断符号位,如果符号位不同则可以直接产生结果;如果符号位相同,这时候做减法,注意此时必然不会溢出,所以减法有效,利用之前的negate方法即可完成这一步,最终将两种情形的结果合并即可:

int isLessOrEqual(int x, int y) {
  int minusx, y_x, r, r0, r1, r2, mask, sx, sy;
  //判断两数是否相同,相同为1,否则为0
  r0 = !(x ^ y);
  //模板,1000
  mask = (1 << 31);
  //符号位,如果为1则为0,否则为1
  sx = !(x & mask);
  sy = !(y & mask);
  //判断符号位是否相同,如果相同返回1,否则返回0
  r1 = !(sy ^ sx);
  //计算-x
  minusx = ~x;
  minusx = minusx + 1;
  //计算y-x,注意可能溢出
  y_x = y + minusx;
  //判断符号位是否为1,如果y >= x,则y_x为全0,否则为全1
  y_x = y_x >> 31;
  //生成结果
  r2 = !y_x;
  //如果r1=1,则返回r2;否则如果r0=1,则返回r0,其余情况返回sy
  r = ((r1) & r2) | ((~r1) & (r0 | ((~r0) & sy)));

  return r;
}

logicalNeg

分两种情形讨论,如果$x<0$,则返回$0$;如果$x\ge 0$,则将其转换为$-x$,然后对$-x$使用之前的判断即可:

int logicalNeg(int x) {
  int r1, r2, r, minusx;
  //首先判断符号位,生成1000,0000
  r1 = x & (1 << 31);
  //右移,生成1111,0000
  r1 = (r1 >> 31);
  //生成1,0,如果x<0则r1=1,否则r1=0
  r1 = r1 & 1;

  //接着判断x>=0的情形,利用-x<=0
  minusx = (~x) + 1;
  //和之前一样判断符号位,如果x>0则r2=1,如果x=0则r2=0
  r2 = minusx & (1 << 31);
  r2 = (r2 >> 31);
  r2 = r2 & 1;

  //生成结果
  r = r1 | r2;
  r = r ^ 1;
  return r;
}

howManyBits

这题比较难,参考了网上的资料:https://www.cnblogs.com/ustca/p/11740382.html#12%E3%80%81howmanybits

首先考虑何时可以减少比特数,分两种情况讨论:

如果$x<0$,注意到有如下事实

假设

那么可以得到对应的$k+1$位表达

所以只需要$k+1$比特(注意需要额外的符号位)即可,最关键的一步是找到从左往右数第一个$0$的位置。

如果$x\ge 0$,显然只要忽略前导$0$即可,假设

那么同样可以得到对应的$k+1$位表达

所以仍然只需要$k+1$比特(注意需要额外的符号位)即可,最关键的一步是找到从左往右数第一个$0$的位置。

为了方便起见,对于负数$x$,我们考虑$\tilde x $,即对于负数

考虑

这一步可以由如下方式完成:

mask = (x & (1 << 31)) >> 31;
//负数则对其二进制取反,保证x为正数;如果s=1,那么mask=1111,x=~x;
x = ((~mask) & x) | (mask & (~x));

所以后面的讨论中我们均考虑$x\ge0$的情形。

现在我们目标转换为找到$x$的二进制表达式中最靠左侧的$1$的位置$k$(假设下标从$1$开始),最终返回$k+1$即可,注意这里有操作数限制,所以应该使用二分查找。

首先考虑前16位,将$x$右移16位得到$x_1$,然后判断$x_1$的二进制表达式中是否含有$1$,得到$c_1$,如果$c_1=1$,那么我们需要考虑$x$的高16位,所以$x$应该右移$16$位,否则只要考虑$x$的低16位,不要需要右移即可,这一步可以由如下代码完成:

//判断前16位是否有1
c1 = !(!(x >> 16));
//如果c1=1则m1=16,否则为0
m1 = (c1 << 4);
x = x >> m1;

其余部分同理,完整的代码如下:

int howManyBits(int x) {
    int mask, m1, m2, m3, m4, m5, m6, c1, c2, c3, c4, c5, c6, res;
    mask = (x & (1 << 31)) >> 31;
    //负数则对其二进制取反,保证x为正数;如果s=1,那么mask=1111,x=~x;
    x = ((~mask) & x) | (mask & (~x));
    //利用二分查找找到第一个1的位置
    res = 0;
    //判断前16位是否有1
    c1 = !(!(x >> 16));
    //如果c1=1则m1=16,否则为0
    m1 = (c1 << 4);
    x = x >> m1;
    //同理判断剩余部分
    c2 = !(!(x >> 8));
    m2 = (c2 << 3);
    x = x >> m2;

    c3 = !(!(x >> 4));
    m3 = (c3 << 2);
    x = x >> m3;

    c4 = !(!(x >> 2));
    m4 = (c4 << 1);
    x = x >> m4;

    c5 = !(!(x >> 1));
    m5 = (c5 << 1);
    x = x >> m5;

    //最后一位特殊处理
    c6 = x & 1;
    m6 = c6;

    //注意要增加1
    res = m1 + m2 + m3 + m4 + m5 + m6 + 1;

    return res;
}

floatScale2

回顾浮点数的数值表示:

以及比特形式:

首先提取上述三个部分:

unsigned r;
int s, exp, frac, mask1, mask2, a;
//1111,1111
mask1 = 255;
mask2 = 0x7fffff;
//8位
exp = (uf >> 23) & mask1;
//23位
frac = uf & mask2;
//s
s = (uf >> 31);

然后根据exp的值分情形判断。

情形1,exp全1,则根据要求输出uf:

if (exp == mask1){
    //nan,无穷大
    r = uf;
  }

情形2,exp全0,这种情况为非规格的浮点数,此时要对frac左移1位(乘2),然后判断frac最高位是否为1,如果为$1$,则将exp增加1,注意此时为转变为规格的情形,frac部分解释为1.xxxx2,所以frac部分无需处理,代码如下:

else if (exp == 0){
    //非规格
    frac = frac << 1;
    //计算第一位
    a = frac >> 23;
    if (a == 1){
        exp = exp + 1;
    }
    r = (s << 31) | (exp << 23) | frac;
  }

然后判断frac最高位为1的情形相对复杂,需要看一个具体例子,以8位浮点数为例,假设浮点数为

注意此浮点数(非规格情形)为

按照上述代码生成的结果为

注意此时为规格情形,$M=1$,所以该浮点数的值为

情形3,exp不是全0也不是全1,这种情形比较简单,直接让exp加1即可:

else if (exp == 0){
    //非规格
    frac = frac << 1;
    //计算第一位
    a = frac >> 23;
    if (a == 1){
        exp = exp + 1;
    }
    r = (s << 31) | (exp << 23) | frac;
  }

完整的代码如下:

unsigned floatScale2(unsigned uf) {
  unsigned r;
  int s, exp, frac, mask1, mask2, a;
  //1111,1111
  mask1 = 255;
  mask2 = 0x7fffff;
  //8位
  exp = (uf >> 23) & mask1;
  //23位
  frac = uf & mask2;
  //s
  s = (uf >> 31);
  if (exp == mask1){
    //nan,无穷大
    r = uf;
  }else if (exp == 0){
    //非规格
    frac = frac << 1;
    //计算第一位
    a = frac >> 23;
    if (a == 1){
        exp = exp + 1;
    }
    r = (s << 31) | (exp << 23) | frac;
  }else{
    exp = exp + 1;
    r = (s << 31) | (exp << 23) | frac;
  }

  return r;
}

floatFloat2Int

第一步和上一题一样,提取s,exp,frac部分:

int r, s, exp, E, frac, mask1, mask2, ofr;
ofr = 0x80000000u;
//1111,1111
mask1 = 255;
mask2 = 0x7fffff;
//8位
exp = (uf >> 23) & mask1;
//23位
frac = uf & mask2;
//s
s = (uf >> 31);

然后根据exp的值分情形判断。

情形1,exp全1,输出ofr:

if (exp == mask1){
    //nan,无穷大
    r = ofr;
  }

情形2,exp全0,这种情况的结果为$\pm 0.f_1f_2\ldots$,所以无论正负,均舍入到$0$:

else if (exp == 0){
    //非规格
    r = 0;
  }

情形3,exp不是全0也不是全1,首先计算$M\times 2^{23}$,注意这里要增加一个前置的1;然后计算实际的E,调整指数即可,这里要考虑溢出的情形;最后根据符号调整结果:

else{
    //当前结果为M*2^23
    //规格情形需要增加前置1
    r = (1 << 23) | frac;
    //实际结果为M*2^E
    E = exp - 127;
    if (E > 23){
        while (E > 23){
            E--;
            r = r << 1;
            if (r < 0){
                r = ofr;
                break;
            }
        }
    }else{
        while (E < 23){
            E++;
            r = r >> 1;
        }
    }
    if (s == 1 && r != ofr){
        r = -r;
    }
  }

完整的代码如下:

int floatFloat2Int(unsigned uf) {
  int r, s, exp, E, frac, mask1, mask2, ofr;
  ofr = 0x80000000u;
  //1111,1111
  mask1 = 255;
  mask2 = 0x7fffff;
  //8位
  exp = (uf >> 23) & mask1;
  //23位
  frac = uf & mask2;
  //s
  s = (uf >> 31);
  //判断
  if (exp == mask1){
    //nan,无穷大
    r = ofr;
  }else if (exp == 0){
    //非规格
    r = 0;
  }else{
    //当前结果为M*2^23
    //规格情形需要增加前置1
    r = (1 << 23) | frac;
    //实际结果为M*2^E
    E = exp - 127;
    if (E > 23){
        while (E > 23){
            E--;
            r = r << 1;
            if (r < 0){
                r = ofr;
                break;
            }
        }
    }else{
        while (E < 23){
            E++;
            r = r >> 1;
        }
    }
    if (s == 1 && r != ofr){
        r = -r;
    }
  }

  return r;
}

floatPower2

注意我们只要计算exp部分即可,其余部分全置为$0$,首先计算exp:

//计算exp
exp = x + 127;

注意exp的范围为0到255,范围之外要特殊处理,完整的代码如下:

unsigned floatPower2(int x) {
    int exp;
    unsigned r;
    //计算exp
    exp = x + 127;
    if (exp < 0){
        r = 0;
    }else if (exp > 255){
        r = 0x7f800000;
    }else{
        r = exp << 23;
    }

    return r;
}