大学上过计算机原理课程的朋友都接触过补码这个概念,不过当时书上所教授的内容都是以二进制作为前提,即所谓的2的补码(2‘s Complement)。近来看TCP/IP Volume 1时,又接触到“1的补码”这个概念,忽然发现其实还不太明白补码到底是什么意思,故查阅资料记录之。
资料来源:维基百科
术语解释:Radix —— 基数,在本篇文章的范畴内等价于“进制”
定义:给出长度为n的数值y,则y的以基数b的补码为: bn - y (即 b的补码)
水平有限,翻译的比较拗口,不过公式还是很简洁的,实际上补码从定义上来说并没有什么难懂的地方。不过有些地方需要加以说明,补码这个概念是建立在进制(即基数)的基础上的,至少在计算机科学的术语中,补码定义中的基数b是一定等于当前进制的,也就是说以上定义可以简化为
给出长度为n的数值y且该数值为b进制,则y的补码为: bn - y
下面以十进制的数作为例子
给出数值y = 1234,很明显长度n = 4,基数b = 10(你说不知道10从哪来的?论审题的重要性)
根据补码的定义那么 y的补码为:104 - 1234 = 8766
用途:补码的作用是什么呢?你走运啦,补码的用途很专一 —— 用加法操作来代替减法操作
听起来匪夷所思,你几乎要脱口而出:“不可能!!!”,不过按照国际惯例,我们先来看看到底怎么回事吧
这里要引入另一个很简单但是英文又很有bigger的术语: 缩小基数补码 (diminished radix complement),看到 diminished 这个词我立马打个激灵,想起了久未谋面的缩小增量排序(diminished increment sort),啥?你说老师没教过这个?噢,它的另一个名字叫希尔排序,它是。。。咳咳,不好意思跑题了。
缩小基数补码实际上就是 (bn - 1) - y,就是说你可以通过往缩小基数补码上加个 1 来得到基数补码,也就是说
bn - y = (bn - 1) - y + 1
是不是想大喊一声:这TM不是废话么??
这个概念有什么用?其实在纯数学的范畴内,这个纯属多余,没有任何用处。然而到了我这个年纪,就会明白一切看起来无意义的东西,肯定一定必须存在一个让它拥有意义的上下文环境。在本文内,这个环境就是“一个数值的长度”,你要知道在数学范围内,你想把一个数写多长就有多长,但是在计算机内数值长是固定的,譬如Java语言的int数值长度为32位,你无法用32位的数去表示33位数,当然34位就更不行了!
回到基数b这个关键字上面来,还是以十进制数为例子
假设数值长度为固定的4,给出数值y = 1234,如果说你真的要按照定义在计算机内去获得补码,你是做不到的,因为根据定义补码为104 - 1234,然而104 = 10000,已经超出了4位数所能表示的范围,你明白了么?缩小基数补码就是为了能在固定的数值长度中去获得补码,所以退一步海阔天空啊
- 104 - 1 = 9999
- 9999 - 1234 = 8765
- 8765 + 1 = 8766
然而,聪明如你一定发现了,说是补码的用途是用加法代替减法,可是在以上第二步获得补码的关键步骤里,不还是要进行减法??这有毛区别?哈,这个就是最让人兴奋的地方,在二进制世界里,你不需要再用减法了,下面以二进制为例子
给出数值y = 1011,那么很明显,按照最新的补码求解步骤,补码 = ( 24 - 1 ) - 1011 + 1,猛然一看,这哪里履行了补码的承诺,用加法代替减法?那么我们就以二进制的视角去看
y = 1011
24 - 1 = 1111
实际上你已经发现了,这个缩小基数补码是固定的——给定计算机数值长度n,则缩小基数补码可以直接写出:pppp...ppp(n个p,p = 进制 - 1),而关键的
1111 - 1011,这一步实际上已经不需要作减法操作了,直接对 y 取反再加上 1 就能得到补码了(是不是对“取反加一”感到特别耳熟?你是个上课听课的好孩纸),在此不得不感谢伟大的二进制!需要再次声明下,这么流畅的操作只有二进制能完成,其他进制想要获得补码,依然需要减法。
终章:在神奇的二进制世界中,获得一个数的补码只需要简单的取反再加上一就可以了。那么最后我们来看看,补码到手之后,又怎么能代替减法呢?
假设需要求解 x - y ( x >= y),那么分为以下步骤
- 求得y的补码 bn - y
- x - y = x + ( bn - y ) = x - y + bn (这一步用补码的加法代替了原数值的减法)
- 显然 x - y + bn >= bn,然而bn已经超出了数的表示范围(overflow),直接被丢弃了,最后的结果就等于x - y