首页 > 代码库 > 负整数为什么存成补码?
负整数为什么存成补码?
——理解补码的正确姿势
一、计算机为什么对负整数使用补码的形式存储
出于简化计算机基本电路的考虑,让加减法都只需要用加法电路实现。所以需要把减去一个正数或加上一个负数都用加上一个正数的方式来表示,于是在存储的时候,负数被直接存储成一种可以直接当成正数加的形式,这种形式就是补码
二、那么补码具体是什么?补码是怎么做到加一个数跟减另一个数一样效果的?
1. 先从时钟这个身边的例子理解 “加一个数跟减另一个数一样效果”
假设你对钟的时候如果发现它是6点,但实际上现在是2点,也就是它走快了4个小时,你可以有两种做法进行校正,一种是逆时针拨回4个小时到2点,另一种是顺时针拨6个小时到12点然后再拨2小时,也就是顺时针拨8个小时。也就是对于时钟的表盘来说,-4 = +8,同样还会有 -1 = +11,-5 = +7,甚至还有 -4 = +8 = +20 = +32 = -16
他们间隐藏了什么规律呢?在数学中,-4、+8、+20、+32、-16可以归为符合某个条件的同一类数字——对于模12同余。wiki上对于模的定义是 “两个整数a、b,若它们除以正整数m所得的余数相等,则称a、b对于模m同余”
而在一个可溢出计数系统中,把计数系统容量作为模,那么所有对此模同余的数在此计数系统中都会有同样的表示(加这个数也一样)。比如时钟表盘就是一个可溢出计数系统,模为12;一个n位二进制构成的计数系统中,因为会舍弃溢出的高位,所以也是一个可溢出的计数系统,模为2^n(从0数到2^n-1)
所以假设一个3位二进制构成的模为8的计数系统,-2,-10,6,14都表示同样的数,也就是减10和加14是一样的效果
2. 引出“补码”
为了让“补码”实现 “加一个正数跟加一个负数(减一个正数) 一样的效果”,“补码”就可以是跟原负数对于模同余的正数
在计算机中为了减少不必要的运算,负数的“补码”就取其中最小的正数,正数直接就是它自己(而且如果负得太多,补一个模都不是正数,那其实算是左边越界溢出)
可能是通过原码求“补码”就是一个补模运算,所以把它叫做“补码”
(但要注意,这里的“补码”都被我打上了双引号,因为这还不是计算机里真正的存储的补码形式,它应该叫补数,不过相信我,已经差不多了)
三、但这种“补码”表示还有问题
通过转换成“补码”,减一个数确实变成加一个数了,看似很不错,但却有一个明显的问题,那就是数本身的符号丢失了
我们只存储了一个在加法运算中方便运算的负数(甚至在结果为负数时计算结果都会不正确),但它却不是一个能正常表示自己的负数(无法逆运算回去)
比如3位二进制,正常能表示0~7,使用补码法能进一步表示 -8~-1(甚至更多) 的运算,但不能真正表示 -8~-1
四、怎么完美解决“补码”的正负表示问题?
不知道人们是怎么想到的,但最后我们看到的这种做法,是真挺完美的。只需要在左边加一个二进制位来表示正负,就能同时实现这几个效果:
- 在保持补码特性的前提下。也就是减一个数还是照样变成加一个数
- 增加正负的表示。能真正表示 -8~-1了,就只用看符号位是0还是1
- 还能让运算时不用另外区分符号位,直接把符号位当成值位进行运算,而结果的正负号自然会符合这个正负表示法(也就是符号位的进位和值位的进位都会自然地合理)(有一种理解方式是,把这个负号1当成减一个模)
具体来说是在左边加一个符号位,这个符号位不参与“补码”的运算,始终表示数的正负,然后在加法运算中可以跟值位一样参与运算,加了一个这样的符号位的“补码”就是真正计算机中存储的补码了
五、所以最后总结一下正常人怎么求补码
正数不变;对负数求最小正同余数(模为二进制值位能存储的数的数量),把它们放入值位,把符号位置为1
六、然后补充一下计算机中求补码的技巧
对于计算机来说,上面这个过程有点繁琐,尤其是需要先求模,然后求最小正同余数,而且这个过程是非常基础的过程,一点点效率提升都能起到非常大的效果
所以这个过程可以被优化成这样:先直接把负数的绝对值存到值位,符号位为负,然后对值位取反+1,就得到了补码(这也是很多教材里告诉我们补码怎么求的方法。。。)
能看到其实优化的是求“补码”的过程:值位取反加一 = 负数的最小正同余数,下面可以证明一下:
- 设一个没有溢出的负数F可以用3位二进制值位表示为:F = -( a*2^2 + b*2^1 + c)(a,b,c ∈ {0,1})
- 对F的值位取反 :F(反) = (1-a)*2^2 + (1-b)*2^1 + 1-c = 2^2 + 2^1 +1 - ( a*2^2 + b*2^1 + c ) = 2^3 -1 + F
- 然后3位二进制的模等于2^3,所以F的补码 F(补) = F + 2^3
- 所以结果就出来了: F(反) = 2^3 -1 + F = F(补) -1 -> F(补) = F(反)+1
不知道为啥目前还没看到解释得这么好理解的补码相关内容,还请博友批评指正~
负整数为什么存成补码?