oynix

于无声处听惊雷,于无色处见繁花

「原码 反码 补码 移码」一探究竟(中)


上文「原码 反码 补码 移码」一探究竟(上)说了基本定义和原码,对于补码,我们只知道是对原码符号位不变,其他位置取反,最后再加 1 得来的,为何如此呢?接下来咱们来揭下「补码」的面具,看看它到底是什么。

0. 关于 1 + (-1)

首先,先看一个问题。

1 的原码为[0000 0001],-1 的原码为[1000 0001],所以计算这两个数相加,应该是这样的:

1
2
3
4
5
6
7
1 + (-1) 

= [0000 0001]原 + [1000 0001]原

= [1000 0010]原

= -2

结果竟然是 -2,很明显是错的,这样用原码计算就出问题了。当然,劳动人民的智慧不可估量,总能发现合适的方式来解决各种问题,于是,补码就诞生了,再看用补码计算的过程。

1
2
3
4
5
6
7
8
9
10
11
1 + (-1) 

= [0000 0001]原 + [1000 0001]原

= [0000 00001]补 + [1111 1111]补

= [0000 0000]补

= [0000 0000]原

= 0

结果正确,问题得以解决,而这也是计算机都是以补码的形式来存储整数的原因。

但是,为什么用补码计算就能得到正确结果呢?为什么补码的计算方式是原码取反再加 1 呢?带着问题,我们继续往下看。

1. 钟表上的哲学

钟表,每个人应该都清楚的,上面的数字范围[0,11],也可以理解为[1, 12],毕竟上面没有写 0 这个数字,但是不变的是,都可以表示12个小时。

比方说,现在是 9:00,时针指向9。我要想知道 7 个小时之前是几点,那么我只需要将时针向回拨动 7 个格子即可,结果很显然,时针将会指向 2,表示 2:00;但是,我要想知道 5 个小时后是几点呢?也很简单,将时针向前拨动 5 个格子,结果也很显然,时针也会指向 2,表示 2:00。

通过不同方式,我们得到了同样的结果,也就是说在钟表上,9 - 7 = 9 + 5 = 2。不仅 7 和 5 有这样的规律,8 和 4、9 和 3等都有这样的规律,也就是说,相加等于 12 的两个数都符合这样的规律,即 X - Y = X + (12 -Y),而 12 在这里有个名字,叫做这个钟表的,12 - Y 叫做 Y 的补数

减去一个数,等于加上这个数的补数,应用这个规律就可以将减法转换为加法了。

那么问题来了,模长该怎么求?

2. 通俗的「模」

通俗的讲,很简单。还是拿钟表举例,上面能表示的数字的总数就是其模长,所以不管是[0, 11],还是[1, 12],都为12。

再来看 8 位二进制,其原码能表示的范围 (注意看,这里说的是原码),**[1111 1111] ~ [0111 1111]**,即 [-2^7 - 1, 2^7 - 1] = **[-127, 127]**,因为我们是要将其全部转变为非负数,即能表示的范围为[0, 127],所以模长为 128。

说完了这些,我们再来重新看下 -3 的补码的计算过程。-3 原码为 [1000 0011],而取反的过程实际上等同于用[0111 1111]减去 -3 原码中的符号位之外的部分,之后再加 1 即得到补码,所以:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-3 补码(未添加符号位)

= [1000 0011]原 取反 + [0000 0001]

= [0111 1111] - [0000 0011] + [0000 0001]

= [0111 1111] + [0000 0001] - [0000 0011]

= [1000 0000] - [0000 0011]

= 128 - [0000 0011]

= 模 - [0000 0011]

= 模 - 3

看到这,是不是一下就明白了?补码实际上就是模减去原码的值,再加上一个符号位,也就是所说的:符号位不变,取反再加1

所以,在计算机中,整数都是以补码的形式存储的,是为了统一加减法运算。因为计算机之中是没有做减法的逻辑门,减法都会被转化为加法来完成计算。

而通过溢出,符号位也可以直接参与计算,大大简化了计算过程,看个例子就明白了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
7 + (-3)

= [0000 0111]原 + [1000 0011]原

= [0000 0111]补 + [1111 1101]补

= [0000 0100]补 (溢出部分不用处理)

= [0000 0100]原

= 4

2 + (-3)

= [0000 0010]原 + [1000 0011]原

= [0000 0010]补 + [1111 1101]补

= [1111 1111]补

= [1000 0001]原

= -1

再看个特例。

1
2
3
4
5
6
7
8
9
-1 + (-127) 

= [1000 0001]原 + [1111 1111]原

= [1111 1111]补 + [1000 0001]补

= [1000 0000]补

= -128

用原码能表示的最小负数为 -127,补码却能表示的最小负数为 -128,但是 -128 没有原码和反码表示,由于计算机中使用补码表示整数,所以这没有影响,因此 8 位二进制数,也就是 byte 类型能表示的范围是 **[-128, 127]**。

说到这,对于补码,应该足够清晰了吧!

------------- (完) -------------
  • 本文作者: oynix
  • 本文链接: https://oynix.com/2019/01/6b6cdea0969b/
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

欢迎关注我的其它发布渠道