CSAPP : Data Lab record

record

整数篇

bitXor

这个比较简单了,没什么好说的

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

tmin

补码的数值计算式:\(-2^{31}·b_{31} + \sum_{i = 0}^{30} 2^i · b_i\)

所以当补码的最高位是1,后面的全都是0,可以得到补码表示法能表示的最小值 \(-2^{31}\)
其补码就是 (1 << 31)

int tmin(void) 
{
  //Tmin = 1000...0000
  return 1 << 31;

}

isTmax

判断传入的数是不是最大数: 01111....111

具体的写到代码里面了

int isTmax(int x) 
{
  //Tmax 的性质-> ~x = x + 1
  //注意到 -1 同样也有这个性质
  //所以判断 !(x + 1) 来排除 -1 的情况
  int y = x + 1;
  return !((~x ^ y) | (!y));
}

allOddBits

判断当前数字其奇数上面是否为全部为1,特别注意最低位是第0位

注意到 \(A_{(16)} = 10_{(10)} = 1010_{(2)},AA = 1010101_{(2)}\),一个十六进制字符占据着四个二进制字节,以此我们扩展到32位,就是 \(AAAAAAAA_{(16)}\) 就是我们要找的一个标准数字\(\text{mask}\),然后我们用传入这个数与mask做 & 运算,看看是否与mask 相等

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

negate

最经典的求解负数,经典公式

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

isAsciiDigit

判断这个字符是不是满足 \(0x30\leq x \leq 0x39\)

其实就是判断 x - 0x30和0x39 - x 是不是负数罢了,x - 0x30 = x + (-0x30) = x + (~0x30 + 1)

int isAsciiDigit(int x) 
{
  int tmin = 1 << 31;
  int inf = x + (~0x30 + 1); //x - 0x30
  int sup = 0x39 + (~x + 1); //0x39 - x
  //检查符号位是否为0
  return !((inf & tmin) | (sup & tmin));
}

conditional

返回条件语句 x?y:z;

首先将 x 变为最好分析的 0000000... 和 1....,使用 x = !x,则 x = 0 时将 x 转化为 1 ,反之将 x 转化为0,然后 x = -x (~x + 1),就能将 0 转化成0000000...,1转化成111111...,(特别注意这里的0/1和最开始是相反的!!!所以后面似 y & ~x 而不是 y & x)然后 y & ~x | z & x来判断该返回哪个

int conditional(int x, int y, int z) 
{
  x = !x; //x为0时变为1,x非0时变为0
  x = ~x + 1; //x为0时变为0x00000000,x非0时变为0xFFFFFFFF
  return (y & ~x) | (z & x);
}

isLessOrEqual

实现一个\(\leq\) 符号

\(diff = y - x = y + (~x + 1)\),我们只需要判断 x,y,diff 三者的符号
首先如果 tagx是1,tagy是0,那么x为负y为正,直接返回1

如果二者符号相同,并且diff的符号位是0,则diff非负,意味着$ 0\leq diff = y - x$,也返回1

int isLessOrEqual(int x, int y) 
{
  int diff = y + (~x + 1); //y - x
  int tagx = (x >> 31) & 1; //x的符号位
  int tagy = (y >> 31) & 1; //y的符号位
  int tagdiff = (diff >> 31) & 1; //y - x 的符号位
  //x和y符号位相同,则看y - x 的符号位
  //x和y符号位不同,则看x的符号位
  return (tagx & !tagy) | (!(tagx ^ tagy) & !tagdiff);
}

logicalNeg

这里是不用 ! 实现 ! 的操作,其实就是判断一个数是不是0

0的重要性质: 0 = -0 ,也就是说我们直接对 x 和 -x 判断其符号位是否相同就好了

这里涉及到了算术右移这个东西,突然发现自己对于 >> 这个符号的理解这么多年没有搞明白,记录一下:

对于有符号整数,C++执行的是算数右移:对于有符号整数 x ,每执行一次 >> 1,相当于在 x 左边加入 x 的符号位(正数补 0,负数补 1),最右边那个位直接舍去。用8位二进制数举个例子:

\[-5_{(10)} = 1111~1011_{(2)},-5 >> 1 =1111~1101 _{(2)} = -3 \]

所以在这里这一步是\((x|(~x + 1)) >> 31\),
对于无符号整数,必须使用逻辑右移,也就是只会补0,没有补1的情况

这时候 如果 x 是 0 我们得到 0,x 非 0得到的是 -1
,最后加上1就好了 (!0 = 1,别忘了)

int logicalNeg(int x) 
{
  return ((x|(~x + 1)) >> 31) + 1;
}

howManyBits

在 Data Lab 题目,比如:

计算“某个数的最高有效位的位置”;
去掉“前导 0”或“前导符号位 1”;
比较“有符号/无符号”的大小。
这些地方说的“有效位”,就是指:

无符号数:去掉左边所有的 0,剩下的那段;
有符号数:
正数:同上,去掉左边的 0;
负数:去掉左边多出来、只用于符号扩展的那一串 1,只保留真正能区分不同负数的那几位。

用人话说就是一些只是为了补足32位或者8位二进制的那些前导0和前导1是不算进去的

在这里还遇到了一个神秘的远古问题
dlc 这个检查工具非常古老,它强制使用 C89 标准。 在 C89 标准中,所有的变量声明必须写在函数的最最最前面,不能夹杂在代码中间

非负数找有效位数比较简单,找到最高位的1就好了,负数因为前导1的存在不能这么简单粗暴找

所以先把负数取反,这样取反后最高位的1(也就是取反前最高位的0),由于不能使用条件语句,我们统一把负数取反,按照正数的处理方式去完成,所以使用 x ^ (x >> 31) 就能完成这个取反了,(x >> 31) 这个东西刚刚在上一题才用了,所以不再细讲

接下来就是模拟一个类似二分/倍增法的过程,判断最高的前 16 8 4 2 1 位是否存在1,然后累加

int howManyBits(int x) 
{
  //首先我们要统一处理正数,所以先把负数转化成正数,其实就是取反,这样方便消去符号位
  int tag = x >> 31;
  int bit_0,bit_1,bit_2,bit_4,bit_8,bit_16;
  //tag = 0000...0 = 0 ,当 x 非负, tag = 111...1111 = -1,x 是负数
  x = x ^ tag; // tag = 0,x^tag = x; tag = -1, x^tag = ~x
  
  //然后就是伟大的倍增法
  
  bit_16 = !(!(x >> 16)) << 4; //是否高16位有1
  x = x >> bit_16; //有1,则右移16位,否则不变,接下来以此类推
  bit_8 = !(!(x >> 8)) << 3;
  x = x >> bit_8;
  bit_4 = !(!(x >> 4)) << 2;
  x = x >> bit_4;
  bit_2 = !(!(x >> 2)) << 1;
  x = x >> bit_2;
  bit_1 = !(!(x >> 1));
  x = x >> bit_1;
  bit_0 = x;
  return bit_0 + bit_1 + bit_2 + bit_4 + bit_8 + bit_16 + 1; 
  //最后加1是因为符号位
}

浮点数篇

因为有点懒得把浮点数的那一大堆东西重新写一遍了,这部分有关浮点数的知识补足就让AI写了一下

浮点数的部分讲解

这里的讨论都是基于32位单精度浮点数 (float)

这部分内容的思维转换非常大。做整数题时,你是按照补码(Two's Complement)思考的;而做浮点数题时,你需要彻底切换到 IEEE 754 标准

在 Data Lab 里,浮点数题目通常给你的参数是 unsigned uf,它代表内存里的 32 个 0/1。你需要把这 32 位拆解开来理解。

1. 宏观蓝图:32 位是如何划分的

内存里的 32 个 bit 被切成了三段:

31 (1位) 30 ... 23 (8位) 22 ... 0 (23位)
S (符号位) exp (阶码) frac (尾数)
  • S (Sign): 决定正负。0 是正,1 是负。
  • exp (Exponent): 决定数量级(2 的多少次方)。
  • frac (Fraction): 决定精度(具体的数值是多少)。

它们共同构成的数值公式通常长这样:

\[V = (-1)^S \times M \times 2^E \]

其中 \(M\) 是由 frac 算出来的有效数字(尾数),\(E\) 是由 exp 算出来的真实指数。


2. 这里的“坑”:三种状态

IEEE 754 并不是一套规则通吃,它根据 exp (中间那 8 位) 的值不同,分成了三种情况。这是你做题时必须判断的第一步。

情况一:规格化数值 (Normalized) —— 最普通的情况

特征exp 的位模式既不是全 0,也不是全 1(即 exp 在 1 到 254 之间)。

这是绝大多数浮点数的样子。

  1. 真实的指数 E
    这里用了一个“偏置值(Bias)”的概念。

    \[E = exp - 127 \]

    • 例如 exp 是 127 (二进制 01111111),那么真实指数 \(E = 127 - 127 = 0\)
    • 例如 exp 是 130,真实指数 \(E = 3\)
  2. 有效数字 M
    这里有个隐含的 1
    frac 部分只存储了小数点后面的数字。计算时,要在前面自动补一个 1.

    \[M = 1.frac \]

    • 例如 frac100...0(二进制),意思是 \(0.1\)(二进制小数),即 \(0.5\)(十进制)。
    • 但实际的 \(M\)\(1.1\)(二进制),即 \(1.5\)(十进制)。

总结公式\(V = (-1)^S \times (1.frac) \times 2^{exp - 127}\)


情况二:非规格化数值 (Denormalized) —— 极小的数

特征exp 全是 0

当数字非常接近 0 的时候,规格化表示不了那么小的数,就切换到这种模式。

  1. 真实的指数 E
    注意!这里不再是 \(0 - 127\),而是固定为:

    \[E = 1 - 127 = -126 \]

    (为什么要这样?为了让它能和规格化数值平滑连接)。

  2. 有效数字 M
    不再隐含 1 了,而是隐含 0。

    \[M = 0.frac \]

  3. 零 (Zero)

    • 如果 exp=0frac=0,这就是数字 0
    • 注意有 +0.0 (S=0) 和 -0.0 (S=1)。

总结公式\(V = (-1)^S \times (0.frac) \times 2^{-126}\)


情况三:特殊值 (Special) —— 无穷与 NaN

特征exp 全是 1 (即 255, 0xFF)

这时候不看公式了,只看 frac

  1. 无穷大 (Infinity)

    • 如果 exp 全 1,且 frac 全 0
    • 表示 \(\infty\)(根据 S 决定是 \(+\infty\) 还是 \(-\infty\))。
    • 这是溢出时的结果。
  2. NaN (Not a Number)

    • 如果 exp 全 1,且 frac 不为 0
    • 表示非数(比如 \(\sqrt{-1}\)\(\infty - \infty\))。
    • 在 Data Lab 里,有时候题目会叫你直接返回参数本身(如果是 NaN 的话)。

3. Data Lab 做题核心思路

既然你要完成浮点数题目,你的工作流程通常是:

  1. 提取
    利用位运算(&, >>)把输入的 32 位整数拆解成 s, exp, frac 三个整型变量。

    • s 取最高 1 位。
    • exp 取中间 8 位。
    • frac 取低 23 位。
  2. 分类讨论
    if 判断 exp 属于上面三种情况的哪一种(是用 == 0,还是 == 0xFF,还是其他)。

  3. 数学操作
    题目通常让你实现 float_abs (绝对值), float_neg (取反), float_twice (乘2), 或者 float_i2f (int转float)。
    你需要在各个分量上做手脚:

    • 乘 2:通常意味着让指数 exp + 1,或者让 frac 左移一位。要小心 exp 加完会不会溢出变成无穷大。
    • 绝对值:最简单,只要把符号位 S 强行变成 0,其他不动。
    • int 转 float:这是最难的。你需要手动计算这个 int 的最高位 1 在哪里(找指数),然后把剩下的位移进 frac 里,还要处理舍入(Rounding)。

4. 快速自测表

给你几个关键的位模式,看看你是否理解了:

位模式 (Hex) exp (8位) frac (23位) 类别 真实含义
0x00000000 0 0 非规格化 0.0
0x80000000 0 0 非规格化 -0.0
0x3F800000 127 0 规格化 \(1.0 \times 2^{127-127} = 1.0\)
0x7F800000 255 0 特殊 +Inf
0x7FFFFFFF 255 非0 特殊 NaN

floatScale2

给你一个无符号序列uf,其代表一个浮点数 x, 返回 2.0 * x
首先基本操作先取出 sign exp frac 这三部分,然后先通过exp判断这个数是否为NaN或者无穷大

然后判断其是否为非规格数(exp = 0)。如果是,检测乘2之后 frac部分会不会 > 2 ,这样exp 会修改为1,*2之后变成了规格数

接下来处理规格数,直接 exp += 1 就好了,要注意判断*2之后会不会变成无穷大就好了

unsigned floatScale2(unsigned uf) 
{
  int sign = uf >> 31; //符号位
  int exp = (uf >> 23) & ((1 << 8) - 1); //指数位
  int frac = uf & ((1 << 23) - 1); //尾数位
  //判断 是否为NaN或无穷大,NaN前面说了返回自身,无穷大的两倍还是无穷大,也返回自身
  if(exp == 0xFF) return uf; 
  //判断是否为非规格化数
  if(exp == 0)
  {
    frac = frac << 1; //尾数左移一位
    if(frac & (1 << 23)) //判断是否变为规格化数
    {
      exp = 1; //变为规格化数,指数变为1
      frac = frac & ((1 << 23) - 1); //去掉隐含的1
    }
    return (sign << 31) | (exp << 23) | frac;
  }
  //规格化数,指数加1
  exp = exp + 1;
  //判断是否变为无穷大
  if(exp == 0xFF) return (sign << 31) | (exp << 23); //尾数为0
  return (sign << 31) | (exp << 23) | frac;

}

floatFloat2Int

给你一个无符号序列uf,其代表一个浮点数 x, 返回 (int)x

首先还是取出 sign exp frac ,顺便计算实际指数 E = exp - 127

首先判断 exp = 0xFF ,这时候是 NaN or 无穷大,统一返回题目要求的无穷大.然后如果实际指数 E < 0,说明这个数是零点几,返回0就好;要是 E >= 31,超出整数的表示范围了,于是就返回无穷大

注意到 frac 这部分的表示是 1.frac 这样,将这一整个(包括隐藏 1 )转化为整数,执行 frac | (1 << 23),如果E >= 23,说明还需要左移 E - 23;E < 23 ,说明左移多了,需要右移 (23 - E)

最后用 sign 的符号判断正负

int floatFloat2Int(unsigned uf) 
{
  int sign = uf >> 31; //符号位
  int exp = (uf >> 23) & ((1 << 8) - 1); //指数位
  int frac = uf & ((1 << 23) - 1); //尾数位
  int E = exp - 127; //实际指数
  int ans;

  if(exp == 0xFF) return 0x80000000u; //NaN或无穷大
  if(E < 0) return 0; //小于1的数,转化为整数为0
  if(E >= 31) return 0x80000000u; //超出 int 范围

  ans = frac | (1 << 23); //加上隐含的1,把 frac 按照正数数值来算

  if(E >= 23) ans = ans << (E - 23); //乘的> 2^23 ,相当于还要左移
  else ans = ans >> (23 - E); //乘的< 2^23 ,相当于右移

  return sign ? -ans : ans; //根据符号位返回正负值
}

floatPower2

给定整数 x,返回 \(2.0^x\)
其实就是计算exp = x + 127,然后注意一下表示范围,判断无穷。还有比\(2^{-149}\)小的数字无法被表示,要返回0

在规格化数范围内,这里的sign 和 frac 全都是0,只和 exp有关

unsigned floatPower2(int x) 
{
    int INF = 0xFF << 23; //表示无穷大的浮点数
    int exp = x + 127; //计算指数位
    if(x < -149) return 0; //太小,返回0
    if(exp <= 0) return 1 << (exp + 22); //非规格化数
    if(exp >= 255) return INF; //太大

    return exp << 23; //返回浮点数表示
}
posted @ 2026-01-12 15:59  SSL_DFT  阅读(0)  评论(0)    收藏  举报