面试题:8 位二进制里 -5 的补码是多少。有人先写原码再取反加一,算到一半忘了符号位要不要动。补码的规则其实很短,难的是想明白为什么这么定。
三步算出负数的补码
以 -5 为例。先写出 5 的二进制 00000101,这就是正数的原码。全部取反得到 11111010,再加 1,结果是 11111011。符号位不单独处理,一起参与取反和加一。再验算:11111011 按无符号读是 251,256 减 251 等于 5,正好是 -5 的绝对值。8 位下 -1 的补码是 11111111,-128 是 10000000,没有对应的正数。
为什么不用原码和反码
原码表示法里 0 有两种写法,00000000 和 10000000,做减法还要判断符号。反码把负数逐位取反,0 依然有两个编码。补码让 0 只有一种表示,减法可以直接变成加法:a 减 b 等于 a 加上 b 的补码。CPU 里只有加法器,这一下省掉了减法电路。8 位补码能表示 -128 到 127,比原码多出一个负数。
位数不同结果不同
同一个负数,8 位、16 位、32 位写出来完全不一样。-5 在 16 位里是 1111111111111011,在 32 位里是 0xFFFFFFFB。做类型转换时,短类型转长类型要符号扩展,高位补 1 而不是补 0。C 语言里把 char 的 -5 赋给 int,结果还是 -5,靠的就是符号扩展。反过来把 int 强转成 char,高位直接截断,值可能变得面目全非。两个正数相加结果变成负数,说明溢出了,补码运算不会报错,只会悄悄回绕。判断溢出看符号位进位和最高位进位是否一致。
几个容易绕晕的点
- 补码的补,是补满模的意思,8 位的模是 256。
- 求补码可以用 256 减绝对值,比取反加一快。
- 最小负数取相反数会溢出,-128 取负还是 -128。
- 无符号数和有符号数混着比较,结果容易反直觉。
- 16 位下 -1 是 0xFFFF,位数越多,高位补的 1 越多。
记住一条:负数补码就是模减去绝对值,写代码时不需要手算,理解这个关系就够了。手算时先确定位数,再动手取反,位数搞错结果全错。