大学的时候就学了补码,但很长一段时间都没有理解。突然某一天看到 Java 的 byte 的类型表示的范围是[128,127][-128, 127],按照最高位是符号位,一个 byte 能表示的最大正数是 128(271{2^7 - 1}),然后负数是存的补码,最小能表示到 -128,那为什么是 -128 呢?这一下勾起了我的兴趣,我要去彻底弄明白这个东西。搜了一波网上的文章,看了之后反正是晕乎乎的,下面将以我的思路来表述一下我对补码的看法。

同余

钟表的例子

图1:
补码原理的个人理解
将时针从 10 点调到 5 点可以将时针逆时针方向拨 5 格,也可以顺时针方向拨 7 格,于是可以看成 5-5+7+7是一样的效果:

  • 时针逆时针方向拨 5 格,相当于做减法:1055{10-5=5}

  • 时针顺时针方向拨 7 格,相当于做加法: 10+7=175(mod12){10+7=17\equiv 5\pmod {12}}

这里的前提是有 12 这个数作为上限的,当和超过 12,则将 12 舍去。于是类推就有减 4 可表示成加 8,减 3 可表示成加 9……

很容易看出:

5 + 7 = 12
4 + 8 = 12
3 + 9 = 12
... 

这些数对的和都一样,称这些数互补。

在数学中,用「同余」概念描述上述关系,两个整数 a、b,若它们除以整数 m 所得的余数相等,则称 a 与 b 对于模 m 同余,记作:
ab(modn){a\equiv b\pmod n}
7 % 12 = -5 % 12 = 7,当 M = 12 时,-5 和 +7,-4 和 +8,-3 和 +9就是同余的。
综上:
10510+7=17(mod12){10 -5 \equiv 10 + 7 = 17 \pmod{12}}
151+7=8(mod12){1 - 5 \equiv 1 + 7 = 8 \pmod{12}}

基于这种模算术 Modular arithmetic,减一个数 x,可以转换成加这个数的 m - x(m 是模)。

二进制补码

以 4 个 bit 位来描述补码吧,4 bit 能表示 16 (24{2^4})个数,16 是模。模也就是能表示的数的个数,这些数都可以在一个环上进行表示,以易于我们的理解。现在假设表示的这 16 个数都是无符号的整数,现在把这 16 个数像钟表一样在一个环上表示:
图2:
补码原理的个人理解
如图,从钟表的例子继续我们的减法运算,假设现在有一个数 x,有如下的计算式的转换:

x - 8 => x + 8
x - 7 => x + 9 
x - 6 => x + 10
x - 5  => x  + 11
...

计算补码:

      /  X        0 <= X <= +7 正数和 0 的补码,就是该数字本身
  [X]补 = |
      \ 2^4 -|X|    -8 <= X < 0 负数的补码,就是用 10000,减去该数字的绝对值

-8 的补码是 8(1000),-7 的补码是 9(1001),-6 的补码是(1010)……

既然减法能转换成加一个补码,那就把这个补码存到计算机中,这样计算机中就没有减运算了。
图3:
补码原理的个人理解
上图中的二进制就是计算机中所存储的数据,首位为 0 的表示正数,最多能表示[0,7]{[0, 7]},不能表示 +8,但可以用补码(2482^4 - 8)表示一个负数 -8(1000)。4 bit 里最小的补码是 1000,最大的补码是 1111。

图2 和 图3 对应的二进制码是没有变化的。图3 变化的是首位为 1 的二进制码都是负数的补码。

看图3,在模是 242^4,4 bit 所能表示的数都在环上标出,x 和 y 是环上的点,
所有的 x -y 都转换成 x + (-y)-y 用补码表示,直接将 x 的二进制和 -y 的补码二进制相加即可得到 x-y 的结果。

通过负数的补码将减法转换为加法,出现的进位就是模,舍去即可。

4 - 2 = 2 
  0100
+ 1110
----------
 10010
这里出现了进位,舍去进位。
结果 10010 超过模了,其实舍去就相当于 10010 - 10000(类似mod M)= 0010。
在圆环里可以很清晰的看出超过模之后就开始新一轮的计数。 

总结

模是 m,在模的基础上,所有要表示的数都可以连续的分布在一个环上,有一个指针(这里假想出来的,方便下面的描述),
x 和 a 都是环上的点, a > 0,x - a 的值就是将指针逆时针拨动 a 格之后指向的值,和顺时针 从 x 处拨动 m - a 格所指向的值是一样的。
-a 和 m - a 对于模 m 同余,m-a 是 -a 的补码。于是计算机中用 m-a 表示 -a,
x - a = x + m - a (模是 m )。

这是我所理解的补码。

最后

4 位二进制数的模是 24=16{2^4}=16
8 位二进制数的模是 28=256{2^8}=256
16 位二进制数的模是 216=65536{2^{16}}=65536
32 位二进制数的模是 232{2^{32}}

4 位二进制补码最多能表示 24{2^{4}}(16 个数),数的范围是 [8,7][-8, 7],
8 位二进制补码最多能表示 28{2^{8}}(256 个数),数的范围是 [128,127][-128, 127],
16 位二进制补码最多能表示 216{2^{16}}(65536 个数),数的范围是 [32768,32767][-32768, 32767],
32 位二进制补码最多能表示 232{2^{32}} 个数,数的范围是 [231,2311][{-2^{31}, {2^{31} - 1}}]

参考:
Why Two’s Complement works
从计算机为什么用补码存储数据,衍生到存储单元数据溢出
Class #7 - Signed Binary Numbers, Subtraction and Overflow
原码、反码和补码

相关文章:

  • 2022-12-23
  • 2022-12-23
  • 2021-12-01
  • 2021-10-12
  • 2022-12-23
  • 2021-11-30
  • 2022-01-08
猜你喜欢
  • 2022-01-19
  • 2022-01-19
  • 2021-10-19
  • 2022-12-23
  • 2022-12-23
  • 2021-12-26
  • 2021-07-05
相关资源
相似解决方案