黑马程序员技术交流社区

标题: 整数在计算机内部的表示 [打印本页]

作者: liuxingxing    时间: 2016-4-19 23:31
标题: 整数在计算机内部的表示
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)。这也就是我们常说的一个负整数的补码表示为它的相反数的补码表示按位取反再加一。
3、整数类取值范围及其分析?
  我们知道整形的取值范围为-2^31~2^31-1,但是我就很纳闷为什么负数哪里没有减1,而整数这里却减1。在加上左边可以利用等差数列计算出来其最大值的确是2^31-1。可是我去计算负数的时候,纳闷了。我们知道正负数的符号位就是第三十一位,而往往符号位是不参与运算的。有些朋友可能会说,10000000000000000000000000000000正好是-2^31,这不正好解释吗。如果时这样的话,那无法解释10000000000000000000000000000001是-2^31+1。既然无法通过计算那么我们怎么知道最小负正数为-2^31?结果是这样的,我们知道整形一共是32位,除过除去符号位只剩下31位,每一位两种可能,所以一共2^31中可能,正整数从0开始,所以最大数为2^31-1(数组有五个元素,最大的小标是4一个原理),而负整数从-1开始,所以是-2^31。
4、为什么负整数不能用正整数的计算方法呢?
  至于负数的二进制表现形式是这样子的,当年计算机工程师想最小的负整数为-2^31,而那一定是除过符号位其他位什么都没有,那一定就是10000000000000000000000000000000,而比最小值大一点的就1就从最右边加一,依次类推,最终-1二进制11111111111111111111111111111111,所以看到一个二进制最原始的计算方法就是,除过符号位把其当做一个整数,算出结果之后减去2^31(加上最小值),但是那种计算方式是在太麻烦。
5、如何写出一个负数的二进制形式呢?
  我们经过观察-1和0的二进制代码互为反码,-2和1的二进制代码互为反码,依次类推,-(x+1)和x的代码互反,所以想要求一个负数的二进制代码,一定是找到代码和其互反的整数的代码,然后取反码运算。假如-8的代码就是先写出7的代码,然后取反码。
6、看到一个负数的二进制如何计算出其十进制的值呢
  我们从第三条知道,看到一个负数的二进制可以先取反码,算出正数,接着加1,取负数就Ok了。-(x+1)和x的代码互反。那可以看成一个负数的代码先取反码,计算出了x,然后加1取负数,就求出了原先的-(x+1)。
7、取反码的快捷运算  
  我们知道-(x+1)和x的代码互为反码,所以一个整数的反码就是先加1在取负数,一个负数的反码也是先加1取负数,所以只需要就只加1取负数。






欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2