目录

计算机信息表示编码中的原码、反码、补码

机器码

要了解标题中的那些码就先了解机器码,机器码就是由0和1组成的数,亦称 Bit,是计算机中信息的表示单位,也是最小的信息表示单位(电子货币比特币的称谓来源于此)。 在机器码中最高位被称为符号位,如果为 0 则表示此为正数,如果为 1 则表示此为负数。

要知道我们的数其实都是有正负之分的,正数的完全写法应该是 +x,通常为了 讨论和书写简单 + 号是被省略的,只有在和负数讨论时为了精准描述我们此时才会将正数的符号显式书写。

为了比较和讨论正数和负数,最好的方法是将正数和负数放在数轴上进行讨论。

我们知道以原点为起点,向左 3 个单位长度所得的值和向右 3 个单位长度所得的值,它们的绝对值是相等的,所不同的仅仅是它们的符号。这两个数在十进制中表示为 +(-)3,而 +3 在计算机的字长为8位,转换位二进制就是 00000011-3 则为 10000011

将两个数对齐比较:

  • 00000011
  • 10000011

可以发现忽略符号位后,其值是一样的,也就是说这两个数的绝对值是一样的。

真值

因为第一位是符号位,所以在机器中的形式值不等于真正的数值,-3 的二进制数是 10000011,其真值就是 -3,但其形式值是 131(无视符号位转换二进制到十进制)。为了区别,计算机的数表示中将符号位的机器数对应的数值称为机器码的真值。

例如:

00000001 的真值是 +00000001,既 +1

10000001 的真值是 -00000001,既 -1

原码

对于一个数,在计算机中是要使用一定的编码方式进行存储的,原码就是存储其中一个具体数字的编码方式。

原码的意思是符号位加上真值的绝对值,解释就是第一位表示符号,其余表示值,那么一个8位二进制的数的表示形式就是:

[符号位]原码 = [+1]原码 = 0000 0001 [符号位]原码 = [-1]原码 = 1000 0001

因为第一位是符号位,所以这个8位二进制的取值范围是 [1111 1111,0111 1111] 这样一个闭区间,转为十进制 就是 [-127,127],这就是一个字节。

反码

反码的定义是:

正数的反码是本身,负数的反码是它的原码符号位不变,其余各位取反。

  • [+1] = [00000001]原码 = [00000001]反码
  • [-1] = [10000001]反码 = [11111110]反码

这是另一种机器存储的具体数字的编码方式,只不过对于人类来说其无法直观理解,通常人类要想理解其真值需要转换成原码再计算。

补码

补码的定义是:

正数的补码是本身,负数的补码是它的原码符号位不变,其余各位取反后再+1(既反码+1)。

  • [+1] = [00000001]原码 = [00000001]反码 = [00000001]补码
  • [-1] = [10000001]原码 = [11111110]反码 = [11111111]补码

依然是人类无法直观看出其数值,需要转换成原码计算其数值。

为什么要使用这么多码

我们现在具有了三种计算机编码方式的概念,那么也就是我们知道对一个数计算机可以用三种方式去表示。对于正数三种方式都是相同的,无需过多解释。 对于负数它们则是完全不同的:

[-1] = [10000001]原码 = [11111110]反码 = [11111111]补码

既然原码才是被人类直接识别的方式,为何要有反码和补码?

对于人类来说,知道第一位是符号位,在计算时我们根据符号位选择真值区域进行加减。但对计算机来说,去判断辨别符号位然后进行计算会让计算机的电路设计变得十分复杂,制造和维护成本高昂!因此科学家发明了将符号位也参与运算的方法。

根据运算法则:减去一个正数,就等于加上一个相同的负数,即 1-1 = 1 + (-1)= 0,所以对于计算机来说实际上是只有加法而没有减法的,这样的设计让电路变得简单了,实现加法计算机只需要一个加法电路实现,而不需单独实现一个加法电路再实现一个减法电路。 同样的道理乘法对于计算机来说就是连续的加上一个数,除法就是连续的加上一个数的相反数。这就是所谓的计算思维,解决一个问题用一种方式不断的重复去完成,而且计算机可以不知疲倦的日夜不断计算。

将符号位参与计算

科学家探索将符号位参与运算,那么计算十进制运算 1-1=0,如果只有原码:

1 - 1 = 1 + (-1) = [00000001]原码 + [10000001]原码
                 = [10000010]原码 
                 = -2

这样得出的是一个错误的数,只用原码无法解决这个问题,为了解决原码做减法的问题,发明了反码:

1 - 1 = 1 + (-1) = [0000 0001]原码 + [1000 0001]原码 
                 = [0000 0001]反码 + [1111 1110]反码 
                 = [1111 1111]反码 = [1000 0000]原码 
                 = -0

补码的引入

使用反码后,减法的计算结果的真值正确了,现在出现一个问题,对于人类来说无论是 +0 还是 -0 ,我们认为都是一样的,因为对我们来说带符号的 0 是没有任何意义的,但是对计算机来说却是 [0000 0000] 原码 和 [1000 0000] 原码 两个编码表示 0。 补码就是为了解决这个问题而出现的:

1 - 1 = 1 + (-1) 
      = [0000 0001]原码 + [1000 0001]原码 
      = [0000 0001]补码 + [1111 1111]补码 
      = [0000 0000]补码
      = [0000 0000]原码

这样 0 就表示为 [0000 0000]-0 则不存在了,而且可以用 [1000 0000] 表示 -128

(-1) + (-127) = [1000 0001]原码 + [1111 1111]原码 
              = [1111 1111]补码 + [1000 0001]补码 
              = [1000 0000]补码

多出来那一个数

-1-127 的结果是 -128,在有补码参与的运算结果中 [1000 0000] 补码 就是 -128,但是实际上是使用以前 -0 的补码来表示 -128,因此 -128 是一个特殊数,并没有原码和反码的表示。

在使用了补码后,不仅仅修复了0 的符号问题,同时还能多表示一个最低数,这就是使用原码和反码表示一个 8 位字节的范围是 [-127.127],而使用补码表示的范围是 [-128,127]

原理简述

计算机科学家巧妙的将符号位作为参与计算的要素,减法变加法这背后是具有数学原理的。

我们将钟表想成一个 12 进制的工具,如果当前时间是 6 点,如果希望将时间设置为 4 点,我们可以这样拨动时针做:

  1. 逆时针拨动 2 小时:6 - 2 = 4
  2. 顺时针拨 10 小时:(6+10) mod 12 = 4
  3. 顺时针拨 22 小时:(10+12) mod 12 = 4

三种方法都可以取到相同结果,逆时针拨动的结果可以替代顺时针的方法,也就是减法替代加法。现在问题被简化成为如何用一个正数替代一个负数,这就要提到数学中的一个概念同余定理。

同余定理

同余定理是数论中的重要概念,定理是这样的:

给定一个正整数 m,如果两个整数 ab 满足 a-b 能够被 m 整除,即 (a-b)/m 得到一个整数,那么就称整数 ab 对模 m 同余,记作 a≡b(mod m)

公式:x mod y = x - yLx / yJ,意义:x mod y 等于 x 减去 y 乘上 xy 的商的下界

-3 mod 2 = -3 - 2xL - 3/2J 
         = -3 - 2xL - 1.5J 
         = -3 - 2x(-2) 
         = -3 + 4 
         = 1

举例:

4 mod 12 = 4
16 mod 12 = 4
28 mod 12 = 4

所以 41628 关于模 12 同余。对于负数:

(-2) mod 12 = 12 - 2= 10
(-4) mod 12 = 12 - 4 = 8
(-5) mod 12 = 12 - 5 = 7

证明

在钟表问题上:

逆时针拨 2 小时 = 顺时针拨 10 小时逆时针拨 4 小时 
              = 顺时针拨 8 小时逆时针拨 5 小时 
              = 顺时针拨 7 小时

那么用数学公式表示就是:

(-2)mod 12 = 10,10 mod 12 = 10;意义:-2 与 10 同余
(-4)mod 12 = 8,8 mod 12 = 8;意义:-4 与 8 同余
(-5)mod 12 = 7,7 mod 12 = 7;意义:-5 与 7 同余

现在运用同余数的性质其中之一反身性 (a ≡ a (mod m)) 与线性运算定理,则有:

7 ≡ 7 (mod 12)
(-2) ≡ 10 (mod 12)
7 -2 ≡ 7 + 10 (mod 12)

为一个负数找到它的正数同余数,即 7 -2 ≡ 7 + 10 (mod 12)。回到二进制码的计算问题上

2-1=2+(-1) = [0000 0010]原码 + [1000 0001]原码
           = [0000 0010]反码 + [1111 1110]反码

-1 的反码是 1111 1110,如果认为这是原码,则 [1111 1110]原码 = -126,去除符号位的话就是 126 ,那么就有:

(-1) mod 127 = 126
126 mod 127 = 126
即
(-1) ≡ 126 (mod 127)
2-1 ≡ 2+126 (mod 127)

也就是说 2-12+126 的余数结果相同,而这个余数就是我们期望的计算机结果:2-1=1。那么证明一个数的反码实际上就是这个数对于一个模的同余数,这个模就是所能表示的最大值,在钟表中表述就是转一圈后总能找到克表示范围内的正确数值。

2+126 是钟表时针转过了一圈,而因为符号位是参与计算的,正好和溢出的最高位形成正确的运算结果。反码已经将减法变成加法,为什么计算机还要使用补码,这样还能得到正确的结果不。

2-1 = 2 + (-1) 
    = [0000 0010]原码 + [1000 0001]原码 
    = [0000 0010]补码 + [1111 1111]补码

现在把 [1111 1111] 当成原码,去除符号位则 [0111 1111]原码 = 127。反码在基础上 +1 的话只是相当于增加了模的值,即: (-1) mod 128 = 127127 mod 128 = 127,即 (-1) ≡ 127 (mod 128),那么 2-1 ≡ 2+127 (mod 128),这样就能得到正确的结果 2-1=1