datalab
CSAPP著名实验:DATALAB
内容是关于计算机信息的表示,主要是位操作、整数题和浮点数相关的题。
实验满分截图
bitXor
只用~和&运算符实现异或操作。
1 | int bitXor(int x, int y) { |
tmin
返回INT_MIN
1 | int tmin(void) { |
isTmax
不使用移位运算符,计算是否是补码最大值。
1 | int isTmax(int x) { |
思路是假设当前x为Tmax,然后将其往0x0
转化。但是需要注意,对于-1此时值也为0.0xFFFFFFFF
0x0
~0xFFFFFFFF
。此时 i = 0,可以利用这个特性把-1给特判掉。
addOddBits
判断所有奇数位是否都为1。
1 | int allOddBits(int x) { |
利用掩码。构造方式是把奇数位设成全1,偶数位设成全0。首先将掩码与x进行与运算,得到y。此时y再与掩码进行异或运算,当且仅当异或结果为0时,才返回true。
negate
给出x,求出-x的补码
1 | int negate(int x) { |
属于常识了。
isAsciiDigit
计算输入值是否是数字 0-9 的 ASCII
值。
1 | int isAsciiDigit(int x) { |
非常神奇的思想。将 转化为 ,减法运算可以通过加上负数的补码实现,大于等于0可以通过判断符号位来实现
conditional
使用位级运算实现C语言中的 x?y:z
三目运算符。
1 | int conditional(int x, int y, int z) { |
应该从答案倒推,首先要想到答案应当是一个或的形式,即答案1|0
或者0|答案2
。这是要再想到掩码,0xFFFFFFFF
和0x0
这两个掩码能够满足本题的需求。正好,0xFFFFFFFF
等于-1,也就是1的负数。那么此时情况就很明朗了。
- 如果条件为true,那么答案1应该与-1进行与运算,答案2应该与0进行与运算
- 如果条件为false,那么答案1应该与0进行与运算,答案2应该与-1进行运算
本题十分重要,后面许多程序都依赖于该函数。
isLessOrEqual
通过位运算实现比较两个数的大小。
1 | int isLessOrEqual(int x, int y) { |
与ASCII那道题不同,直接相减可能会有溢出的情况发生。再仔细想想,发现溢出的情况只有可能是符号不同。所以就先把符号不同特判掉就好了。刚刚写过的conditional函数活学活用。
logicalNeg
判断一个数是否为0。
1 | int logicalNeg(int x) { |
在补码中,只有0和INT_MIN的取反加一后的结果为本身。但是0的符号位为0,INT_MIN的符号位为1。也就是说,只有0满足两个符号位的或运算结果为0。通过这个性质就可以写出答案。
howManyBits
求值:一个数用补码表示最少需要几位?
1 | int howManyBits(int x) { |
这题我想了好久好久好久。。。
用类似树状数组的思想求解~~(不太认同这是二分)~~。
首先需要对负数进行处理,因为负数补码高位用1填充,我更倾向于用0填充。一是方便理解二是不用特判。然后无视开头的符号位0,最后算贡献的时候再把这一位加上。
先看高16位的结果是否为0:
是0?往更低位找。
不是0?至少需要低16位,算上低位的贡献。通过右移运算淘汰掉低位,在更高位上找答案。
以120x0000000C
为例
第一次,右移16位,未找到。
第二次,右移8位,未找到。
第三次,右移4位,未找到。
第四次,右移2位,找到了。算上贡献2。0x00...011
。
第五次,右移1位,找到了。算上贡献1。0x00...01
。
第六次,右移0位,找到了。算上贡献1。
最后的答案就是2+1+1=5。
以2980x0000012A
为例
第一次,右移16位,未找到。
第二次,右移8位,找到了。算上贡献8,舍弃低位后变成0x000001
。。。
第六次,右移0位,找到了。算上贡献1.
最后的答案就是8+1+1=10。
floatScale2
给一个浮点数,返回浮点数 2的值。
1 | unsigned floatScale2(unsigned uf) { |
代码的分类讨论已经很清楚了。如果是无穷大或者nan,那么依然是无穷大和nan。如果是非规格化,就是小数部分左移一位,溢出没关系这说明规格化了。如果是规格化,就是阶码+1,此时特判溢出的情况。
floatFloatToInt
将一个浮点数转化为整数。
首先特判0的情况,比较特殊。
如果阶码小于0,说明这个数字小于1,那么会被整形舍入,也返回0。
整形能表示的范围远小于浮点数,要根据阶码判断一下是否溢出。
最后就是根据尾数和阶码进行移位操作得到答案。由于阶码是无符号数,所以要根据浮点数的符号位决定返回正数还是负数。
1 | int floatFloat2Int(unsigned uf) { |
floatPower2
返回与 等效的位级别。
1 | unsigned floatPower2(int x) { |
分四种情况分类讨论。
非规格化exp恒定为,float为-126。frac共有23位。理论上非规格化单精度浮点数最多能表示的精度。
第一种情况,精度不够,直接返回0。
第二种情况,非规格化,看看这个数比-149大了多少。
第三种情况,规格化,逆推出阶码的大小后,右移23位即可。
第四种情况,溢出,返回无穷大。
总结
虽然实验过程很坎坷偷看了别人代码,但是所有代码都搞懂了,以后有机会再二刷吧。本次实验的基础收获当然是关于信息的位级表示相关的内容了,对一些位级运算符更加熟悉了一些。