1. 无符号整数的表示
我们知道,无符号整数在计算机内部是以二进制的形式存储的,比如我们在C语言中声明并初始化一个变量:
int i = 66;假设我们针对的机器是32位机器,字节顺序(byte order)为小端(little endian)。由于int类型是32位的,66这个数字就会被存储为它的32位二进制表示(01000010 00000000 00000000 00000000),共占据4个存储单元。
这样一来,32位二进制数所能表示的无符号整数共有2^32个,范围为[0, 2^32 - 1]。然而,我们需要计算机计算的内容常常会包含负整数,所以先得使得计算机能够“认识”负整数。这就要求我们制定一种规则,对于给定的32位二进制数,这种规则要能够准确的说明它表示的是负整数还是正整数,并进一步说明值是多少。在这种需求下,人们首先发明了原码这种能同时表示正整数和负整数的编码规则。原码的规则很简单,拿32位二进制数来说,把它的最高有效位(most significant bit,msb)规定为符号位,1的话这个数就是个负数,0的话这个数就是个正数,其余的31位来表示这个数的值是多少。也就是说原码把32位拆成了1位的符号位和31位的数值位序列。
然而原码存在两个问题,一是数值0的表示有两种:一种是msb为1其余位为0(-0),一种是msb为0,其余位为1(0);还有一个更头疼的问题是互为相反数的两个数(除了0)相加不等于0...于是人们又发明了反码。反码也是msb作为符号位,然后其余的31位,将一个数的低31位按位取反,即可得到它对应的相反数。在反码这个编码体系下,两个互为相反数的数相加倒是等于0了,不过数值0依旧有两种形式,一个32位全为0的+0,一个是32位全为1的-0。
我们不仅仅想让数值0只有一种二进制表示,而且我们还想让有符号数和无符号数能使用同一套加法器及乘法器。基于这些需求,补码便诞生了。
2. 补码表示
在补码表示中,是将32位二进制数能表示整数的范围劈成两半,一半给负整数,一半给非负整数。那么很自然的划分成一半一半的方法就是根据msb为0或1来划分。补码表示下,msb为1表示这个数是负整数,为0表示是非负整数。
(1)有符号整数与无符号整数
我们先来理清一下无符号整数和有符号整数的概念。首先,我们要知道的是,计算机压根就不认识有符号和无符号,对于计算机来说,有符号整数和无符号整数都是一串位序列罢了。一串32位的位序列究竟代表是有符号数还是无符号数,完全是由上下文决定的。比如内存中有这样一串位序列:11000000 00000000 00000000 00000000。假如我们现在知道它表示一个整数,我们是否能知道它究竟是正数还是负数吗?答案是不能。除非我们知道这个位序列是一个int型变量的值,那么我们可以知道它是有符号整数,所以这串序列表示-64;而如果这是一个unsigned型变量,它的值就是193。所以,所谓有符号整数和无符号整数,就是一种上下文。在有符号整数这个上下文的前提下,我们说 11000000 00000000 00000000 00000000表示-64,因为它的符号位为1;而在无符号整数这个上下文下,所有的数都被看做是正的,根本没有符号位这一说,所以相同的位序列就表示192。
(2)为什么选择补码表示
说到补码表示的优越性,我们先要来了解一个概念:同余运算。在这之前,我们先介绍下模运算(mod)的概念,模运算和取余运算(一般用“%”表示)的区别在于取余运算计算商时向0取整,模运算计算商时向负无穷取整。例如,-3 % 5的值-3,计算过程是将商-0.6向0取整得到0,然后-3 - (0 * (-3))得到结果为-3。而-3 mod 5的值为2,计算过程是将商-0.6想下取整得到-1,然后-3 - ( -1 * 5 )得到2。现代计算机的加法与乘法运算单元实际上进行的都是“同余运算”。在32位体系下,我们定义2^32为这个同余体系下的模。我们用同余式表示一个同余运算,“≡”为同余符号,相当与等式中的等号。加法器计算1 + 1实际上进行的就是同余加法,大致分如下三步:
- a ≡ 1 (mod 2^32)
- b ≡ 1 (mod 2^32)
- c ≡ a + b ≡ 2 (mod 2^32)
那么我们来看一下,在32位体系下,(x mod 2^32)的结果范围为[0, 2^32-1]。特别的,我们注意到:2^32 - 1 ≡ -1 (mod 2^32) ≡ 2^32 - 1 (mod 2^32),也就是说-1与2^32 - 1是同余的,同理,-2 与 2^32 - 2,-3 与 2^32 - 3都是同余的。那么我们就可以定义11111111 11111111 11111111 11111111表示-1和2^32 - 1,前者是在有符号整数的上下文中,后者是在无符号整数的上下文中。
我们再来看一下这个同余体系下,-x与x的关系。假设x是一个32位序列,很容易知道x + ~x会得到11111111 11111111 11111111 11111111,。用同余式表达就是:x + ~x ≡ 2 ^32 - 1 ( mod 2^32)。而x + ~x + 1 ≡ 2^32 (mod 2^32) = 0 (mod 2^32)。因此在这个同余体系下我们可以得到-x ≡ ~x + 1 (mod 2^32)。这也就是我们常说的一个负整数的补码表示为它的相反数的补码表示按位取反再加一。