理解二进制的原码、反码、补码

1. 十六进制

16 进制数逢 16 进 1,2 进制逢 2 进 1。2 ^ 4 = 16。

16 进制进一位,恰好对应二进制进四位

十六进制 10  = 二进制 1 0000
十六进制 100 = 二进制 1 0000 0000

从而,16 进制数(如: 2A9),可以按位依次转成二进制(每一位对应二进制四位):

2(0010)A(1010)9(1001)。

即:

2A9 = 0010 1010 1001

故,当看到十六进制数 fffffff3 时,就能立即方便地反应出其二进制的数值了。

  • 二进制

      (binary) 0000 0011
    
  • 八进制

      (octal) 325
    
  • 十进制

      (decimal) 289
    
  • 十六进制

      (hex) ffff fff3
    

2. 数据在内存中存储的方式

以 16 位系统为例,i 在内存中占两个字节,结构如下:

(binary) 0000 0000 0000 0011

为了方便,设系统为8位的,int 只占一个字节。即 int i = 3 的存储结构为:

(binary) 0000 0011

3. 溢出与"模"的概念

从上可以看出,int 占一个字节的话,能储存的数的范围是 (binary) 0000 0000 -> (binary) 1111 1111,或者说 (decimal) 0 -> (decimal) 255

如果两个数想加,超过了这个范围,那么就会"溢出"。

例如,在这个存储中, (decimal) 4 + (decimal) 254 = ?

从二进制的角度看:

  (binary) 0000 0100 = (decimal) 4
  (binary) 1111 1110 = (decimal) 254
-------------------------------------  +
(binary) 1 0000 0010 = (decimal) 258

因为只能存储8位,溢出的那个 1 就不得不被丢掉, 于是就只剩下 (binary) 0000 0010 了。

也就是说:(decimal) 4 + (decimal) 254 = (decimal) 2 (丢弃溢出丢出来的奇怪结果,是后面能进行负数运算的根源所在。

丢掉的那个溢出数,即 (binary) 1 0000 0000 = (decimal) 256,就被称为 "模"。

"模"可以理解为存储空间的长度(从 0 到 255,长度正好是 256)。当两个数的和超过了存储空间的长度时,取其对模的余数(如:258 mod 256 = 2)。

4. 为什么要用补码?

按前面假设,int 只占一个字节,即8位存储结构。很明显,这里只能存储指定范围的正数,且只能进行加减运算(结果也只能属于这个范围,溢出的就丢掉)。

负数怎么办呢?

计算机是不认识负数的。负数可以理解为减运算。

根据前面的丢弃溢出的运算,有:

(decimal) 4 + (decimal) 254 =  (decimal) 2

而显然的,还有:

(decimal) 4 - (decimal) 2 = (decimal) 2

也就是说,在丢弃溢出的运算条件下:

+ (decimal) 254 = - (decimal) 2

是不是说 + 254 = - 2 ?!

在这种丢弃溢出的算法中,是可以这样的。(可以理解为钟表往前拨8小时(+8),与向后拨4小时(-4),是一样的结果)

也就是说,在存储范围内,如果 a + b = "模",那么,在丢弃溢出的运算条件下:

+ a = - b;
- a = + b

那么,就可以直接用 + 254 表示 - 2 啦!(因为它们在运算中的效果是一样的!)

从而 (binary) 1111 1110,既可以表示 + 254, 也可以表示 -2

可是,怎么让计算机知道,我要的值是 - 2,而不是 + 254 呢?

在前面再加个标志位。比如,加个 0 表示要取的是 + 254,加个 1 表示我们要取的是 -2。

可是存储就只有8位, 最后只好将存储中第一位的值拿出来作为符号位(0表示是正数,1表示是负数),剩下的7位用来存储数值,如下:

0 000 0011

第一位 0 是符号位。结果为: 符号位 = 0,值 = (binary) 000 0011

嗯,原先8位存储结构,能存放数值为 0 => 255,现在为了支持负数,第一位用来做符号位,于是能表示的数值范围变成了 - 128 => + 127(取 1 000 0000 = - 128)。

显然,现在数值的"模"也变成了 128。与前面相同运算,与 - 2 对等的正数也变为 + 126 了。

于是,根据 - 2 = + 126,将 - 2 写成二进制:

1). 符号保留在符号位,即,符号位 = 1 (0表示是正数,1表示是负数)

2). 数值则直接用 + 126,即数值为 111 1110

结果:

- 2 = 1 111 1110

而正数的二进制表示则简单、直观得多:

+ 126 = 0 111 1110

这样,只需将8位存储结构拿出第一位来表示正/负符号,我们就可以存储正/负数,并进行加减运算了(负数用对应的正数表示,反正运算结果是一样的;而减运算就是加负数。

5. 结论

  • 原码:

    对任一个数,将其转换为二进制。然后在前面按"0表示是正数,1表示是负数"加一个符号位,就是原码,如:

      (decimal) 2 的原码 = (binary) 0 000 0010
      (decimal) -2 的原码 = (binary) 1 000 0010
    
  • 反码:

    对正数,等于其原码:

      (decimal) 2 的反码 = (binary) 0 000 0010
    

    对负数,顾名思义,就是对原码的数值的部分取反。如:

      (decimal) -2 的反码 = (binary) 1 111 1101
    
  • 补码:

    对正数,等于其原码:

      (decimal) 2 的反码 = (binary) 0 000 0001
    

    对负数,等于反码再加1:

      (decimal) -2 的反码 = (binary) 1 111 1110
    

    正是前面用 + 126 表示出来的 - 2 的值!

    与负数相对等的那个正数,它们绝对值的和正好等于"模"(可以理解为钟表往前拨8小时(+8),与向后拨4小时(-4),是一样的效果)。对一个二进制数来讲,先取反,再加1,再加上原数,正好是一个和为"模"的过程,反映到十进制上就是通过-2找+126的过程。

    所以说,负数在计算机中是用补码表示的。

    对于零而言,(binary) 0 000 0000 的补码为 (binary) 1 000 0000,所以就取 (binary) 1 000 0000 为 -128。所以,负数的表示范围为: - 128 => + 127。

最近更新: 10/7/2018, 7:49:52 PM