如何理解补码?

本文尝试用更加浅显和本质的方式去理解补码。

1. 原码与补码

人类习惯于用十进制数进行运算,而计算机的每个位却只有0和1两种状态,换句话说,计算机采用的进制是二进制。

因此,我们面临的第一个问题就是计算机如何用二进制来表示十进制数字。

对于正数而言,可以直接用该数的二进制形式来表示,例如,十进制数2,其在计算机中的表示为(假设计算机的字长为8):

0000 0010

但是这样做会带来一个问题,如何表示负数呢?为此,我们采用牺牲最高位的办法,将其视作符号位,并用1表示负,0表示正(这样做是可以接受的,因为虽然正数的表示范围由之前的 [1, 255] 缩小到了现在的 [1, 127], 但是我们却因此收获了 [-127, -1] 的负数表示范围;另一方面,引入符号位后,[1, 127] 内的正数的表示方式并没有发生改变,换句话说,引入符号位前后,正数的表示方法是相洽的)。

例如,十进制数-2,其二进制表示现在变为:

1000 0010

这里,我们便可以引入原码的概念

原码:原码是指一个二进制数左边加上符号位后所得到的码,且当二进制数大于0时,符号位为0;二进制数小于0时,符号位为1。[1]

原码很直观,人类可以直接理解原码。

但是原码是不是完美的呢?很显然,它是有缺陷的。

1)十进制数0的原码并不唯一,以下两个都是0的原码。

0000 0000
1000 0000

2)当进行有负数参与的运算时,原码将暴露更大的缺陷,例如:-3+2:

  1000 0011 #-3的原码
+ 0000 0010 #2的原码
------------
  1000 0101 #-5的原码

我们得到了-5,这显然是个错误结果。

为此,我们需要介绍补码这个新概念

补码:正数和0的补码就是该数字本身。负数的补码则是将其对应正数按位取反再加1。[2]

例如,2的补码即为:

0000 0010

-2的补码为:

# 1. 将-2对应的正数2,按位取反得到:1111 1101
# 2. 1111 1101再加1即得到: 1111 1110,为-2的补码
1111 1110

0的补码只有一个,为:

0000 0000

现在我们再来验证一下补码在进行加减运算时是否能得到正确结果,同样是-3+2:

  1111 1101  #-3的补码
+ 0000 0010  #2的补码
------------
  1111 1111  #-1的补码

可以看到,我们得到了正确答案。

事实上,补码是计算机真正用来表示二进制数的方式,验证如下:

#include<iostream>
using std::cout;

int main()
{
	unsigned int a = -1;
	cout << a;  //输出结果为255,即1111 1111, 而1111 1111正是-1的补码
	return 0;
}

2. 为什么补码能起作用?

我们知道数轴上的点与实数是一一对应的,先来看看我们最熟悉的十进制数在数轴上的排列:

在这里插入图片描述
因此,对于数轴上的任意一个数,其加上一个正数,则表示其在数轴正方向上移动了一段距离;而减去一个正数(等价于加上一个负数),则表示其在数轴负方向移动了一段距离。这是确保我们在进行数的加减时得到正确结果的关键。

当我们引入了原码,那么数在数轴上又是如何排列的呢,可以验证,它们的排列变成了下面这个样子:

在这里插入图片描述
注意:0在这里是一个关键的分界线,对于负数而言,它们的“0” 是1000 0000;而对于正数而言,它们的“0”是0000 0000。

仔细观察,我们会发现,这个数轴有两个正方向(这里我们把加上一个正数,数字移动的方向定为正方向):当一个负数加上一个正数,数字往左边移动,例如:-3+2,结果变成了-5;当一个正数加上一个正数,数字往右边移动,例如3+2,我们得到5. 这就解释了为什么原码在加减运算中会出错的原因。

回到补码,如前所述,一个负数的补码就是是将其对应正数按位取反再加1。但是这里对于补码的叙述并不直观,甚至会让人有点摸不着头脑,为了更好地理解补码究竟在做什么,我们不妨将-2的补码与其对应正数2的补码相加,看看会得到什么结果:

  0000 0010  # 2的补码
+ 1111 1110  #-2的补码
------------
1 0000 0000 

这样,我们就发现了对一个负数求补码的本质:

负数 A 补 A_补 A = 模-绝对值(A)

模的值,等于 2 n 2^n 2n,n为字长。例如,对-2求补码:

-2补= 2^8 - 0000 0010#2的补码
    = 10000 0000 - 0000 0010
    = (1111 1111 - 0000 0010) + 1
    #即等于2先按位取反,再加1

因此,引入补码后,数字在数轴上的排列变成了下面这个样子:

在这里插入图片描述
这里,不仅0只有一个表示,而且数轴也只有唯一一个正方向,从而保证了运算的正确性。而这个保证,仔细思考一下就会发现,正是利用了负数A的绝对值与其补码之和等于模,从而将负数部分的数轴方向调转。

注:考虑到计算机存在“溢出”的现象,因此更为正确的表示应该采用数环,而不是数轴,不过,理解用的话,数轴足够了。

总结

  1. 为什么要引入补码?
    答: 1. 0的原码有两个,处理起来不方便;2. 原码不能直接相加减;

  2. 引入补码的好处?
    答:1. 0的补码只有一个;2. 变减法为加法,即减法和加法共用一套底层电路;3. 补码之间可以直接相加减,最高位可以接受进位。

参考

[1] https://zh.wikipedia.org/wiki/原码
[2] https://zh.wikipedia.org/wiki/补码

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
补码溢出是计算机中一种常见的数值表示问题,当使用补码来表示有符号整数时,可能会出现溢出情况。 补码是一种将负数进行二进制表示的方法。在使用补码表示有符号整数时,最高位(最左边位)表示符号位,0表示正数,1表示负数。对于一个n位的补码数,其范围为-2^(n-1)到2^(n-1)-1。 补码溢出是指进行数学运算时,结果超出了补码所能表示的范围。例如,假设使用8位补码来表示整数,对于补码数11111111(-1的补码),当再加上1时,按照数学运算结果应该为0,但在补码中,将会溢出。因为补码的范围为-128到127,所以计算结果超出了这个范围。 判断补码溢出需要理解数值计算中的溢出规则。对于两个有符号整数a和b,若它们的符号位相同(即正数相加或者负数相加),但结果的符号位与它们不同,就发生了补码溢出。例如,假设a和b都是正数,但是它们的和是负数,就说明发生了补码溢出。 倒a是指对a进行取反操作,即将其各位取反(0变为1,1变为0)。在补码表示中,倒a等价于对a取补码,即对a的各位取反并加1。例如,倒5的补码为-5,倒-5的补码为5。 综上所述,补码溢出判断就是要观察参与运算的数的符号位,通过符号位的变化来判断是否发生了补码溢出。而倒操作则是将一个数的各位取反并加1,得到它的补码表示。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值