Brief
说来惭愧虽然刚接触计算机时已经学过原码、反码和补码的内容,但最近重温时却发现“这是什么鬼东西”,看来当初只是应付了考试了而已。本篇将试图把他们说个明白,以防日后自己又忘记了。
在深入之前,我们先明确以下几点:
1. 本篇内容全部针对有符号数整数;
2. 对于有符号数整数,其在计算机中的存储结构是 符号位 + 真值域。其中符号位为0表示正数,1表示负数;
3. Q:既然已经有原码,那么为什么还要出现反码、补码等数值的编码方式呢?
A:由于为了降低当时计算机物理电路的设计难度,决定采用加法代替减法运算(因此计算机内部是没有减法运算的),即10-5被替换为10+(-5),而反码、补码就用于解决10+(-5)的问题的。
True Form
原码(称为true form、sign-magnitude 或 sign and magnitude),就是直接将十进制数转换为二进制数形式。如7的原码为0111,-6的原码为1110。
注意:
1. 原码是区分+0和-0的,+0的原码为0000;-0的原码为1000;
2. 若存储空间为n bit,则原码的取值范围是 -2n-1 ~ 2n-1。
原码在以加法代替减法的运算中引起的问题:
例如在计算0 = 1-1 = 1+(-1) = 0001 + 1001 = 1010 = -2, 发现通过原码来运算时居然会得到0 == -2的结果。于是引入了反码。
一般用于IEEE 754浮点数标准中尾数(significant)的表示。
Ones' Complement
原码转换成反码的规则如下:
1. 正整数原码的反码是其自身。如原码0001的反码是0001;
2. 负整数原码的反码则是对原码真值域的个位数取反即可。如原码1010的反码是1101。
那么将反码转换为原码的规则如下:
1. 正整数反码的反码是其自身。如反码0001的原码是0001;
2. 负整数反码的原码则是对反码真值域的个位数取反即可。如反码1101的原码是1010。
注意:
1. 反码是区分+0和-0的,+0的反码为0111;-0的反码为1111;
2. 若存储空间为n bit,则反码的取值范围是 -2n-1 ~ 2n-1。
反码在以加法代替减法的运算中引起的问题:
例如在计算0 = 1-1 = 1+(-1) = 0001【原码】 + 1001【原码】 = 0001【反码】 + 1110【反码】= 1111【反码】 = 1000【原码】 = -0, 发现通过反码来运算时居然会得到0 == -0的结果。
看到采用反码运算的结果已经非常接近正确结果,但-0对于我们来说还是没有太多的意义。于是引入了补码。
Two's Complement
原码转换成补码的规则如下:
1. 正整数原码的补码是其自身。如原码0001的补码是0001;
2. 负整数原码的补码则是对原码真值域的个位数取反后,整体+1即可。如原码1010的补码是1110。
那么将补码转换为原码的规则如下:
1. 对补码再求一次补码则得到原码。
取补码的流程发生在符号变化时,也就是正、负数间转换。如-(1),-(-1)等。
具体流程如下:
1. 符号位取反;
2. 真值域取反;
3. 整体+1。
因此在进行-(1)运算时,步骤如下(1的补码是0001):
1. 符号位取反——1001
2. 真值域取反——1110
3. 整体+1——1111
或将步骤1和2合并为对各位取反——1110,然后整体+1——1111
在进行-(-1)运算时,步骤如下(-1的补码是1111):
1. 符号位取反——0111
2. 真值域取反——1000
3. 整体+1——1001
或将步骤1和2合并为对各位取反——1000,然后整体+1——1001
注意:
1. 补码是不区分+0和-0的,+0和-0的补码为0000;
2. 若存储空间为n bit,则反码的取值范围是 -2n ~ 2n-1。
3. 原码 + 其补码 = 0。
补码在以加法代替减法的运算的结果:
例如在计算0 = 1-1 = 1+(-1) = 0001【原码】 + 1001【原码】 = 0001【反码】 + 1110【反码】= 0001【补码】+ 1111【补码】 = 0000【补码】 = 0000【原码】=0, 发现通过补码来运算时结果恰恰正确。
真相:有符号整数其实是以补码的编码方式存储的。因此C语言的int类型在32位OS上的值范围是:-2n ~ 2n-1。我们可以通过以下的C代码片段要验证:
#include <stdout.h> #include <string.h> int main(){ int f = -1; unsigned long l; int i; char s[32]; memcpy(&l, &f, 4); for (i = 31; i >=0; --i){ if (l % 2 == 1){ s[i] = '1'; } else{ s[i] = '0'; } l /= 2; } s[32] = '