csapp实验一:datalab
一、实验目的
本实验目的是加强学生对位级运算的理解及熟练使用的能力。
二、报告要求
本报告要求学生把实验中实现的所有函数逐一进行分析说明,写出实现的依据,也就是推理过程,可以是一个简单的数学证明,也可以是代码分析,根据实现中你的想法不同而异。
三、函数分析
符号只能使用位运算符 ! ~ & ^ | + << >> ,不能使用 if, else, ?:, while
bitXor函数
函数要求:
| 函数名 | bitXor | 
|---|---|
| 参数 | int , int | 
| 功能实现 | x^y | 
实现分析:
a^b = ~(~(a&~b)&(~(~a&b)))
异或的逻辑是 x ^ y = (x AND ~y) OR (~x AND y)
根据de morgan律:A | B = (A & ~B)
函数实现:
int bitXor(int x, int y) {return ~(~(x&~y)&(~(~x&y)));}
getByte函数
函数要求:
| 函数名 | getByte | 
|---|---|
| 参数 | int , int | 
| 功能实现 | 在word x中提取第n个字节 | 
实现分析:
getByte(x,n) = (x>>(n<<3))&0xff
从一个 32 位整数中取出第 n 个字节(0 表示最低字节)。
- 每个字节是 8 位,所以要移动 n * 8位,即n << 3。
- 再用掩码 0xFF(11111111)保留最后 8 位。
函数实现:
int getByte(int x, int n) {return (x>>(n<<3))&0xff;}
logicalShift函数
函数要求:
| 函数名 | logicalShift | 
|---|---|
| 参数 | int , int | 
| 功能实现 | 将x向右移n位 | 
实现分析:
在函数中新定义了两个变量y和z,其中:
普通的 >> 是算术右移,会补符号位(即1或0)。
逻辑右移则始终补 0。
解决方法:
- 先算术右移 x >> n;
- 再用掩码清除被符号位填充的部分。
掩码思路:
(1 << 31) 是最高位为 1 的数。
((1 << 31) >> n) << 1 构造出 n 位的 1,再取反即为低 32-n 位为1的掩码。
函数实现:
int logicalShift(int x, int n) {int mark = 0x01 << 31;int y = (x & mark);int z = ~((y>>n)<<0x1);return (x>>n) & z;
}
bitCount函数
函数要求:
| 函数名 | bitCount | 
|---|---|
| 参数 | int | 
| 功能实现 | 计算输入字节中为1的bit数 | 
实现分析:
- 原理:一个2bit的二进制数,其所有的组合有00, 01, 10, 11。若要计算这个2bit的数的二进制有多少个1,则可以用这个数减去其二进制第二个位上的数字,得到的便是这个2bit数字的二进制中的1个个数。
- 核心思想(并行计数 / 分治):
 把 32 位分成很多小块,先在最小粒度上对相邻位成对计数,然后把结果在更大粒度上合并,逐层累加,最终得到总和。这样做避免了循环和条件判断,全部用位运算和加法完成,速度快且合法操作数有限。
- 掩码(便于分组):
- mask1 = 0x55555555 = 二进制每 2 位为 01 01 01 ...
 用于把每对位分别取出(位0与位1配对)。
- mask2 = 0x33333333 = 每 4 位为 0011 0011 ...
 用于把每 4 位当作两组 2 位的和来合并。
- mask4 = 0x0F0F0F0F = 每 8 位为 00001111 ...
 用于把每 8 位当作两组 4 位的和来合并。
函数实现:
int bitCount(int x) {int m8 = 0x55; int mask1 = m8 | (m8 << 8) | (m8 << 16) | (m8 << 24);int m3 = 0x33;int mask2 = m3 | (m3 << 8) | (m3 << 16) | (m3 << 24);int m0f = 0x0f;int mask4 = m0f | (m0f << 8) | (m0f << 16) | (m0f << 24);x = x + (~(((x >> 1) & mask1)) + 1);x = (x & mask2) + ((x >> 2) & mask2);x = (x + (x >> 4)) & mask4;x = x + (x >> 8);x = x + (x >> 16);return x & 0x3f;
}
conditional函数
函数要求:
| 函数名 | conditional | 
|---|---|
| 参数 | int , int , int | 
| 功能实现 | 实现x ? y : z | 
实现分析:
核心思想是:
我们想构造一个掩码(mask),它是全 1 或全 0。
- 当 x ≠ 0时 →mask = 0xFFFFFFFF
- 当 x == 0时 →mask = 0x00000000
制造掩码:
mask = !!x;        // 把 x 变成 0 或 1
mask = ~mask + 1;  // 扩展成全 0 或全 1
- !!x:两次逻辑非,确保结果只有 0 或 1- 比如:x = 5 → !!x = 1
 x = 0 → !!x = 0
 
- 比如:
- ~mask + 1:- 当 mask = 0 → (~0 + 1) = 0
- 当 mask = 1 → (~1 + 1) = 0xFFFFFFFF
 
函数实现:
int conditional(int x, int y, int z) {int mask = (!!x << 31) >> 31;return (mask & y) | (~mask & z);}
tmin函数
函数要求:
| 函数名 | tmin | 
|---|---|
| 参数 | void | 
| 功能实现 | 返回最小补码 | 
实现分析:
最小的补码就是0x10000000,直接返回
函数实现:
int tmin(void) {return 0x1<<31;}
fitsBits函数
函数要求:
| 函数名 | fitsBits | 
|---|---|
| 参数 | int , int | 
| 功能实现 | 若x能用n个bit表示则返回1,否则返回0 | 
实现分析:
整体思路:
- 用 n 位能表示的数,其符号扩展后应与原值相同;
- 左移丢弃高位再右移回来,看是否变了。
函数实现:
int fitsBits(int x, int n) {int shift = 32 + (~n + 1); // 32 - nreturn !(((x << shift) >> shift) ^ x);}
dividePower2函数
函数要求:
| 函数名 | dividePower2 | 
|---|---|
| 参数 | int , int | 
| 功能实现 | 计算x/(2^n),其中0<=n<=30 | 
实现分析:
正数右移n位即可;
负数需要补偿偏差使结果“向零取整”(而非向下取整)。
函数实现:
int dividePower2(int x, int n) {int sign = x >> 31;int bias = (1 << n) + ~0;  // 2^n - 1int add = sign & bias;return (x + add) >> n;}
negate函数
函数要求:
| 函数名 | negate | 
|---|---|
| 参数 | int | 
| 功能实现 | 取相反数-x | 
实现分析:
直接按位取反加1即可
函数实现:
int negate(int x) {return ~x+1;
}
howManyBits函数
函数要求:
| 函数名 | howManyBits | 
|---|---|
| 参数 | int | 
| 功能实现 | 计算一个整数 x 用补码表示所需的最少二进制位数。返回表示x所要用的最小bit数 | 
实现分析:
| x | 二进制补码 | 最少位数 | 
|---|---|---|
| 12 | 01100 | 5 | 
| 298 | 0100101010 | 10 | 
| -5 | 1011 | 4 | 
| 0 | 0 | 1 | 
| -1 | 1 | 1 | 
| 0x80000000 | 1000...000 | 32 | 
要知道要多少位表示 x,实际上是找:
- 对于正数:最高的 1 在第几位
- 对于负数:最高的 0 在第几位(因为负数补码前面是 1)
所以要对数处理:
- 对正数:不变;
- 对负数:按位取反,相当于找“最高的 1”。
然后用位级的二分来进行查找最高的1
最终 b16 + b8 + b4 + b2 + b1 + b0 + 1 就是所需的位数。
加 +1 是因为还要包含符号位。
函数实现:
int howManyBits(int x) {int b16, b8, b4, b2, b1, b0;int sign = x >> 31;    /* sign = 0 (x >= 0) or -1 (x < 0) */x = (sign & ~x) | (~sign & x);  /* if x negative, set x = ~x, else keep x */b16 = !!(x >> 16) << 4; /* if top 16 bits nonzero, add 16 */x >>= b16;b8 = !!(x >> 8) << 3;   /* if top 8 bits nonzero, add 8 */x >>= b8;b4 = !!(x >> 4) << 2;   /* if top 4 bits nonzero, add 4 */x >>= b4;b2 = !!(x >> 2) << 1;   /* if top 2 bits nonzero, add 2 */x >>= b2;b1 = !!(x >> 1);        /* if top 1 bit nonzero, add 1 */x >>= b1;b0 = x;return b16 + b8 + b4 + b2 + b1 + b0 + 1;
}
isLessOrEqual函数
函数要求:
| 函数名 | isLessOrEqual | 
|---|---|
| 参数 | int , int | 
| 功能实现 | 若x<=y则返回1,否则返回0 | 
实现分析:
判断大小转换成看两数之差 y - x >= 0,但是两数异号直接计算可能会溢出
所以我们必须先判断 x 与 y 的符号是否相同:
情况1:x 与 y 符号不同
- 如果 x 是负的,y 是正的 → 一定成立 (x ≤ y)
- 如果 x 是正的,y 是负的 → 一定不成立 (x ≤ y)
情况2:x 与 y 符号相同
- 用正常的差值判断:y - x >= 0
函数实现:
int isLessOrEqual(int x, int y) {int sx = x >> 31;          /* sign bit of x: 0 or -1 */int sy = y >> 31;          /* sign bit of y */int diff = sx ^ sy;        /* 1 if signs differ */int sub = y + (~x + 1);    /* y - x */int ssub = sub >> 31;      /* sign of (y - x) *//* If signs differ and x negative, x <= y. Else if signs same, check y-x >= 0 */int res = (diff & sx) | ((!diff) & (!ssub));return !!res;
}
intLog2函数
函数要求:
| 函数名 | intLog2 | 
|---|---|
| 参数 | int | 
| 功能实现 | 计算log2(x)并向下取整 | 
实现分析:
这个问题的实质是找最高位的1在哪一位
用二分法查找最高位的方式来实现:
设一个变量 ans = 0,表示当前找到了最高位的下标。
从高位向低位检测是否有 1:
- 如果 x >> 16不为 0 → 说明最高位至少在 [16, 31],那么ans += 16
- 再检测 x >> 8不为 0 →ans += 8
- 再检测 x >> 4不为 0 →ans += 4
- 再检测 x >> 2不为 0 →ans += 2
- 再检测 x >> 1不为 0 →ans += 1
ans 就是 ⌊log₂(x)⌋。
函数实现:
int intLog2(int x) {int ans = 0;int b16, b8, b4, b2, b1;/* check top 16 bits */b16 = !!(x >> 16) << 4; /* if high 16 bits set, add 16 */ans += b16;x >>= b16;/* check top 8 bits */b8 = !!(x >> 8) << 3;ans += b8;x >>= b8;/* check top 4 bits */b4 = !!(x >> 4) << 2;ans += b4;x >>= b4;/* check top 2 bits */b2 = !!(x >> 2) << 1;ans += b2;x >>= b2;/* check top 1 bit */b1 = !!(x >> 1);ans += b1;return ans;
}
floatAbsVal函数
函数要求:
| 函数名 | floatAbsVal | 
|---|---|
| 参数 | unsigned | 
| 功能实现 | 返回浮点数f的绝对值 | 
| 要求 | 可以使用运算符, | 
实现分析:
- 取绝对值:
 清除符号位即可(符号位在最高位)。
 即uf & 0x7FFFFFFF。
- 检测 NaN:
 当指数全为 1(e = 0xFF000000)且尾数不为 0(f != 0)时,是 NaN。
函数实现:
unsigned floatAbsVal(unsigned uf) {unsigned mask = 0x7FFFFFFF;unsigned abs = uf & mask;if (abs > 0x7F800000)return uf;return abs;
}
floatScale1d2函数
函数要求:
可以使用 integer/unsigned, operations, ||, &&. also if, while
| 函数名 | floatScale1d2 | 
|---|---|
| 参数 | unsigned | 
| 功能实现 | 返回0.5*f | 
功能:
返回与表达式 0.5 * f位级等价的无符号整数。
- 输入 uf是一个单精度浮点数的位模式;
- 输出同样是一个位模式;
- 若 uf是 NaN,则返回原值。
实现分析:
- 若输入为INF和NaN,那么直接返回
- 若exp>1,保留小数位然后直接把exp-1,阶码也就减少1,达到了*0.5的目的
- exp<=1,此时若直接把exp-1,那么exp将会小于等于0,实现不了f*0.5;
- 所以将uf右移1位实现"exp-1"(若exp=1那么将会得exp=0,若exp=0那么exp的值不变)和小数部分的f*0.5。
- 但是在这里要注意小数的舍入问题,若f的第0位和第1位都是1,那么需要给他加2。
函数实现:
unsigned floatScale1d2(unsigned uf) {int exp = (uf & 0x7fffffff)>>23;int sign = uf & 0x80000000;if((uf&0x7fffffff) >= 0x7f800000) return uf;if(exp > 1)    return (uf&0x807fffff)|(--exp)<<23;if((uf&0x3) == 0x3) uf = uf + 0x2;return ((uf>>1)&0xbfffffff)|sign;
}
floatFloat2Int函数
函数要求:
可以使用 integer/unsigned, operations, ||, &&. also if, while
| 函数名 | floatFloat2Int | 
|---|---|
| 参数 | unsigned | 
| 功能实现 | 将f转换为int形式 | 
实现分析:
- 提取符号位 S、指数 E 和尾数 F
- 计算真实指数 exp = E-127
- 恢复尾数隐含 1 → M
- 特殊情况:
- E=255 → NaN/∞ → 返回 0x80000000
- exp < 0 → |f|<1 → 返回 0
- exp > 30 → 超出 int → 返回 0x80000000
 
- 根据 exp 调整尾数得到整数部分:
- exp > 23 → 左移
- exp ≤ 23 → 右移
 
- 根据符号返回正负整数
函数实现:
int floatFloat2Int(unsigned uf) {unsigned sign = uf >> 31;int exp = ((uf >> 23) & 0xFF) - 127;unsigned frac = uf & 0x7FFFFF;unsigned M = frac | 0x800000;unsigned val;if (((uf >> 23) & 0xFF) == 0xFF) return 0x80000000;if (exp < 0) return 0;if (exp > 30)return 0x80000000;if (exp > 23) {val = M << (exp - 23);} else {val = M >> (23 - exp);}return sign ? -val : val;
}
