CSAPP-datalab

date: 2020-04-12

本实验除个别题目借鉴了其他博客(有标注),其余题目均为博主自己的解法,不保证最优。

1. bitAnd

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

第一题要求使用取反以及或运算来实现与运算。

这题非常简单,使用离散数学中的德摩根定律就可以求解,有兴趣可以自己去搜索,这里不做展开介绍。

2. getByte

/* 
 * 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;
}

第二题要求取出某一字节的值。

只需将该字节移动至低八位并使用与运算取出即可,不需要考虑算术移位等问题,n << 3 等价 n * 8

3. logicalShift

/* 
 * 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 
 */
int logicalShift(int x, int n)
{
    return (x >> n) ^ (((x & (1 << 31)) >> n) << 1);
}

第三题要求使用算术移位实现逻辑移位。

由于算术移位会在高位补符号位,要实现逻辑移位需要将负数高位补的 1 转变为 0,同时保证非负数高位的 0 依旧是 0,所以我采用取出符号位拓展至 n-1 位并异或的方法。当然,取反之后再进行与运算也是一种选择。

由于该题限制,只能使用 0x00 - 0xff 范围内的值,所以采用了上述方法,如果不对字面值常量做限制,则使用如下代码即可求解:

return (x & 0xffffffff) >> n;
// 不能使用 (x | 0) >> n 或者 (x ^ 0) >> n 等

经测试,整型变量 (测试过 intlong long int ) 与字面值常量进行与运算之后,其结果类似无符号类型 (可能是被拓展成了更大的数据类型,其符号位为 0 ),所以可以实现逻辑移位的效果。

4. bitCount

/*
 * bitCount - returns count of number of 1's in word
 *   Examples: bitCount(5) = 2, bitCount(7) = 3
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 40
 *   Rating: 4
 */
int bitCount(int x)
{
    int a = 0x55 | (0x55 << 8);
    int b = 0x33 | (0x33 << 8);
    int c = 0x0f | (0x0f << 8);
    int d = 0xff | (0xff << 16);
    int e = 0xff | (0xff << 8);
    a = a | (a << 16);
    b = b | (b << 16);
    c = c | (c << 16);
    x = (x & a) + ((x >> 1) & a);
    x = (x & b) + ((x >> 2) & b);
    x = (x & c) + ((x >> 4) & c);
    x = (x & d) + ((x >> 8) & d);
    return (x & e) + ((x >> 16) & e);
}

第四题要求计算 int1 的个数,可以将 32 位相加,最终的和就是 1 的个数。

由于操作符限制为 40 个,逐位相加必然无法实现,需要尽可能在一次操作内完成多位相加,采用分治归并的思想可以解决问题(类似归并排序):

  • 首先将 32 位分为 16 组,每组为连续 2 位,令每组前后 1 位相加,得到的值存储在该 2 位中
  • 再将 32 位分为 8 组,每组为连续 4 位,将 4 位的前后 2 位相加,得到的值存储在该 4 位中
  • 依次类推,最终得到 32 位相加的值
  • n 位长度的数只需要相加 \lceil \log_{2}{n} \rceil 次即可

参考自:https://www.cnblogs.com/graphics/archive/2010/06/21/1752421.html

5. bang

/* 
 * bang - Compute !x without using !
 *   Examples: bang(3) = 0, bang(0) = 1
 *   Legal ops: ~ & ^ | + << >>
 *   Max ops: 12
 *   Rating: 4 
 */
int bang(int x)
{
    return ((((~x + 1) | x) >> 31) + 1) & 1;
}

第五题要求使用位运算来实现 ! 的功能。

由于 0 取非为 1,任何 非0值 取非均为 0,所以我们可以考虑如何判断值为 0。我们可以通过 取反+1 来得到一个数的负数。由于 0 的负数还是 0,而 非0值 的负数依旧是 非0值,且必定有一个数的符号位为 1。所以 非0值 的原值和其取负数之后的值进行或运算,其符号位必定为 1,可以通过其符号位来实现取非操作。

6. tmin

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

第六题要求返回二进制最小整数的补码。

根据二进制补码的编码规则,最小数符号位为 1,其余位全 0

7. fitsBits

/* 
 * 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
 */
int fitsBits(int x, int n)
{
    x >>= n + ~0;
    return ((((x + 1) | (~x + 1)) >> 31) + 1) & 1;
}

第七题要求判断 x 是否可以被 n 位的二进制补码所表示,其实就是要求我们判断 x 是否满足 -2^{n-1} \leq x \leq 2^{n-1}-1

简单推导可以发现,满足条件的 x,除去低 n-1 位,其余位必定相同,即全 0 或全 1。所以可以利用算术移位的特性,使 x 右移 n-1 位,则右移后的值应当全 0 或者全 1(x + 1) | (~x + 1) 的值只有在 x0 或者全 1 时,符号位才为 0,之后再判断符号位即可。

8. divpwr2

/* 
 * 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
 */
int divpwr2(int x, int n)
{
    int fs = x >> 31;
    int f = fs & 1;
    int result = ~(((~x + f) >> n) + fs);
    return result | (((f & !!result) << 31) >> n);
}

第八题要求求出 x/(2^n) 的值,结果向 0 舍入。

如果 x 是非负数,则直接右移 n 位即可,所以负数则可以考虑先转换成正数,右移完之后再转换成负数。

由于最小值 (32 位下的 0x80000000) 取负数之后会产生溢出,其结果还是其本身,导致移位之后再次取负数时可能变成一个正数,所以需要判断这种情况并将高 n 位置 1,同时要避免结果为 0 时误将值更改为负数。

9. negate

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

第九题要求使用位运算实现取负数。

这题非常简单,在之前的题目中已有多次运用。

10. isPositive

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

第十题要求判断一个数是否为正数。

我们只需要通过符号位来判断是否是非负数,并排除 0 的情况即可。

11. 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)
{
    int t = x + (~y + 1);
    int ft = t >> 31;
    int fx = x >> 31;
    int fy = y >> 31;
    int fneq = fx ^ fy;
    return ((ft | !t) & !fneq) | (fx & !fy);
}

第十一题要求判断 x,y 是否满足 x \leq y

判断 x,y 是否满足 x \leq y,只需要判断 x-y \leq 0 即可。由于可能存在溢出,我们在 x,y 异号时直接判断 x,y 的大小,不通过减法来判断。((ft | !t) & !fneq) 判断同号的情况,(fx & !fy) 判断异号的情况。

12. ilog2

/*
 * ilog2 - return floor(log base 2 of x), where x > 0
 *   Example: ilog2(16) = 4
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 90
 *   Rating: 4
 */
int ilog2(int x)
{
    int t = (!!(x >> 16)) << 4;
    t += (!!(x >> (t + 8))) << 3;
    t += (!!(x >> (t + 4))) << 2;
    t += (!!(x >> (t + 2))) << 1;
    t += !!(x >> (t + 1));
    return t;
}

第十二题要求求出 \lfloor \log_{2}{n} \rfloor 的值。

经过简单推导可以发现,我们只需要找到最高位的 1 即可。如果逐位查找,在 90 次操作的限制内不可能完成 31 位的查找,所以我们可以采取二分法:每次将查找区间分为两份,首先使用两次取非来判断高位是否存在 1,若有则在高位区间继续二分查找,若没有则在低位区间继续二分查找,直到区间长度为 1

13. float_neg

/* 
 * float_neg - Return bit-level equivalent of expression -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 representations of
 *   single-precision floating point values.
 *   When argument is NaN, return argument.
 *   Legal ops: Any integer/unsigned operations incl. ||, &&. also if, while
 *   Max ops: 10
 *   Rating: 2
 */
unsigned float_neg(unsigned uf)
{
    int E, M, f;
    E = (uf << 1);
    E >>= 24;
    M = (uf << 9) >> 9;
    f = (E + 1) || !M;
    return uf ^ (f << 31);
}

第十三题要求对一个浮点数求负数,在值为 NaN 的情况下返回原值。

求负数比较简单,对符号位取反即可,主要需要判断是否为 NaN。我们将阶码取出,并拓展至 32 位,如果全 1 则阶码全 1 ,值可能为 NaN 。再判断 M 部分是否全 0,若不全 0 则是 NaN,返回原值。

14. float_i2f

/* 
 * float_i2f - Return bit-level equivalent of expression (float) x
 *   Result is returned as unsigned int, but
 *   it is to be interpreted as the bit-level representation of a
 *   single-precision floating point values.
 *   Legal ops: Any integer/unsigned operations incl. ||, &&. also if, while
 *   Max ops: 30
 *   Rating: 4
 */
unsigned float_i2f(int x)
{
    int t = 0x80000000;
    int count = 31;
    unsigned result = x & t;
    unsigned tail;
    if (!x)
        return 0;
    if (x < 0)
        x = -x;
    while (!(x & t))
    {
        x <<= 1;
        --count;
    }
    tail = x & 0xff;
    x = (x & ~t) >> 8;
    result |= ((count + 127) << 23) | x;
    if (tail > 0x80)
        ++result;
    else if ((tail == 0x80) & result)
        ++result;
    return result;
}

第十四题要求将 intx 转换为 float 型,并将其编码输出。

float 型编码可以分解为 s-e-f 格式。

  • 首先取出符号位,存入 result
  • 之后我们需要得到除符号位以外的最高位的 1 所在的位数,其位数即是最低位到其的距离,也就是小数点移动的位数,位数 +127 即是阶码的移码
  • 最高位的 1 之后的 23 位即是浮点数的小数。如果最高位的 1 之后超过 23 位,则需要进行向偶舍入。将其取出并在低位补足 8 位,判断是否大于 0.5,如果相等则判断尾数末位是否为 1,若是则进位。进位时若尾数部分溢出则阶码需要 +1

15. float_twice

/* 
 * float_twice - 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 float_twice(unsigned uf)
{
    int E;
    E = (uf << 1);
    E >>= 24;
    if (!~E)
        return uf;
    if (E == 0)
        return (0x80000000 & uf) | (uf << 1);
    return uf + 0x00800000;
}

第十五题要求对给定编码的浮点数,返回 2 倍的值,如果为 NaN 则返回原值。

其中有一隐含条件:如果值为 INF 也返回原值,所以不需要判断是否为 INF

所以我们只需要判断阶码是否全 1 即可。

  • 若全 1 则返回原值
  • 若全 0 则令其尾数左移 1 (若溢出则阶码 +1 )
  • 若既有 0 又有 1 则阶码 +1

个人博客:https://wilfredshen.cn/

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,036评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,046评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,411评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,622评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,661评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,521评论 1 304
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,288评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,200评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,644评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,837评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,953评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,673评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,281评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,889评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,011评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,119评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,901评论 2 355