2的补码

计算机的底层是一堆的bit,具体怎么理解bit,取决于我们怎么解释,对于同样的8个bit,例如 11111111,我们可以把它看成是一个无符号数,也可以看成是一个有符号数。 如果我们把它看成是一个无符号数,那么它的值就是255,如果我们把它看成是一个有符号数,那么它的值就是-1, 如果理解为字符,那么它就是一个字符,具体是什么字符,取决于编码方式,如果是ASCII编码,那么它就是DEL字符。

对于无符号数,我们可以直接用二进制表示,例如 11111111 就是255,计算机很轻易就能理解二进制,但是人类比较熟悉十进制。我们看看换算过程:

1*2^7 + 1*2^6 + 1*2^5 + 1*2^4 + 1*2^3 + 1*2^2 + 1*2^1 + 1*2^0 = 255

但是现实情况中,数字不仅有正数,还有负数,那么我们怎么表示负数呢?我们可以用补码的方式来表示负数。

原码

原码是最简单的一种表示方法,就是用最高位来表示符号,0表示正数,1表示负数,其余位表示数值。例如,00000001表示1,10000001表示-1。

原码的优点是,非常直观,可以直接看出一个数是正数还是负数,但是原码的缺点也很明显,就是加减法非常麻烦,因为要考虑符号位。

比如我们要计算1+1,首先我们要找到1的原码,然后再相加,最后再判断符号位。

1的原码:00000001
1的原码:00000001
相加:00000010
换算成十进制:2

但是,如果我们要计算1-1,首先我们要找到1的原码,然后找到-1的原码,然后再相加,最后再判断符号位。

1的原码:00000001
-1的原码:10000001
相加:10000010
换算成十进制:-2

可以看到,计算结果是错误的,这是因为原码的计算中,符号位也参与了计算,导致结果错误。

因此有了反码的出现。

反码

反码是在原码的基础上,对负数的数值部分取反。例如,00000001表示1,11111110表示-1。

反码的优点是,加减法非常简单,只需要把两个数的反码相加即可,不需要考虑符号位。

比如我们要计算1+1,首先我们要找到1的反码,然后再相加,最后再判断符号位。

1的反码:00000001
1的反码:00000001
相加:00000010
换算成十进制:2

如果我们要计算1-1,首先我们要找到1的反码,然后找到-1的反码,再相加,最后再判断符号位。

1的反码:00000001
-1的反码:11111110
相加:11111111
换算成十进制:-0

但是反码的缺点也很明显,就是有两种0,正0和负0,这样会导致一些问题,因此有了补码的出现。

什么是补码

补码是一种用来表示负数的方法,它的特点是,正数的补码就是它本身,负数的补码是它的绝对值的二进制取反加1。

例如,我们要表示-1,首先我们要找到1的二进制表示,然后取反,最后加1,就是-1的补码。

1的二进制表示:00000001
取反:11111110
加1:11111111

所以-1的补码是11111111。

如果我们要计算1+1,首先我们要找到1的补码,然后再相加,最后再判断符号位。

1的补码:00000001
1的补码:00000001
相加:00000010
换算成十进制:2

如果我们要计算1-1,首先我们要找到1的补码,然后找到-1的补码,再相加,最后再判断符号位。

1的补码:00000001
-1的补码:11111111
相加:00000000
换算成十进制:0

我们可以看出,2的补码有以下优点:

  1. 0的补码是00000000,所以0只有一种表示方法,不会出现正0和负0的问题。
  2. 两个数相加,只需要把它们的补码相加,然后舍弃最高位的进位,就是最终的结果。
  3. 两个数相减,只需要把减数的补码取反加1,然后和被减数的补码相加,然后舍弃最高位的进位,就是最终的结果。
  4. 补码的表示范围是-2^(n-1)到2^(n-1)-1,其中n是bit的位数,而原码和反码的表示范围是-2^(n-1)+1到2^(n-1)-1。

可以看到,补码的形式中,只有一个0,所以不会出现正0和负0的问题,这样就避免了一种情况,同时,两个数相加和相减的操作也变得非常简单,只需要把补码相加即可,简化了硬件的实现。因此, 补码才会被广泛选择作为表示负数的方法。

总结

记得初学计算机的时候,觉得补码难以理解,工作以后,才慢慢理解,现实情况中,我们往往会因为实际情况拖鞋,补码就是如此,舍弃了易理解性,但是提高了计算的效率,这就是现实情况中的折中。

软件工程中,到处都是取舍,我们要根据实际情况,选择最合适的方法,往往我们都找不到完美的方案,但是却有一个最合适的方案。


更多文章
  • Blackbox禁用IPv6
  • 预防缓存击穿
  • Go 1.13的errors挺香
  • flutter开发体验汇报
  • 自己封装一个好用的Dart HTTP库
  • Flutter应用启动后检查更新
  • Grafana Gravatar头像显示bug修复
  • flutter中使用RESTful接口
  • Vim YouCompleteMe使用LSP(以dart为例)
  • flutter webview加载时显示进度
  • SQLAlchemy快速更新或插入对象
  • 修复Linux下curl等无法使用letsencrypt证书
  • 欣赏一下K&R两位大神的代码
  • MySQL的ON DUPLICATE KEY UPDATE语句
  • 使用microk8s快速搭建k8s