这里先将二进制的计算可以分为两类,加减运算与乘除运算,本篇讨论的是加减运算。
(一)预备知识——数据在计算机的存储与表示
从一个问题入手:java中byte(1字节,8位)的取值范围为:-2^7<=取值范围<=2^7-1,或者写成-128<=取值范围<=127,为什么呢?
疑问:按照我们一般认为,如果8位中一位作为符号,那么应该是11111111<=取值范围<=01111111,或者写成-127<=取值范围<=127,-128从何而来?
首先从原码讲起,原码即为计算机中对数值的二进制表示,如 5用二进制表示为0000 0101 ;
其次就是反码,顾名思义就是取反,对于正数来说,反码与原码相同;对于负数来说,反码为原码的各位取反(符号位除外),如(0011 0111)反= 0011 0111 (1101 0010)反= 1010 1101 ;
再次就是补码,计算机中,数值一律用补码表示和存储的,正数的补码与原码相同,负数的补码为“其反码+1”,如(0101 1101)补=0101 1101 (1101 0010)补=1010 1110
关于补码,我们做进一步解释:
(1)化减为加:
由于计算中的CPU只有加法器,没有减法器,所以在计算机采用原码做减法是会存在这样的问题:对于1-1=0
看做1+(-1)=0 二进制表示 0001+1001=1001 变成了十进制的负1而不是0。补码的出现很好的解决了这个问题,由于采用补码运算,则补码加法成为:[X+Y]补 = [X]补 + [Y]补同时,补码的减法变为:[X-Y]补 = [X]补 - [Y]补 = [X]补 + [-Y]补 ;在此我们以减法为例说明补码的优势,还以上述为题为例:(0001)补+(1001)补 = 0001 + 1111 = 0000 (最高位的进位省略),这样就顺利得到了0
(2)10000000与00000000都是零吗?——关于-128的由来
补码还解决了原码中存在两个0 的问题(即+0 和 -0),以8进制为例,java中byte的取值范围应该是-127~ -0和+0~ 127 即存在-0和+0 ,但是在两个0转换为补码后,分别为10000000和00000000,00000000它还是0,但是我们人为地规定10000000表示-128,这就是上面取值范围中-128的由来。这里再强调一下,10000000和00000000都是补码,而不是原码。
(3)一开始的问题,我们得出了“符合逻辑”的疑问,这个问题又出在哪?
其实问题出在,我们一直在用原码在看问题,而不是补码。这里先举一个我再微博中说过的例子,形象地说明一下什么是补码:
“时钟的例子:0时和6时差(10-6)=4时(顺时针),但是你也可以这样写(10+6)-12=4时(逆时针),后一个式子中的6就是“补码”,12表示“定长”,减12表示“溢出”。“定长”和“溢出”也是“化减为加”的关键。这是一个从高维空间看低维空间而变得更加简洁的典型。”
我们再用一个实际的例子说明一下如何用“高维看低维”:
类比时钟的例子,我们将表中的补码稍微转换一下位置,写在一维数轴上,再将数轴收尾相连,就形成了一个新的“时钟圆盘”。
那么我们来试试一个例子:3-1=(011-001)原码运算=(011+111)补码运算=010=2
也就是说我们可以写成3-1=2(顺时针运算),也可以写3+7-8=2(逆时针运算)
这就是用超时空理论,从高维去看地位的一种解释方法。
(二)加减法运算
(1)原码到补码的转化:符号位是要单独提取出来的,不参与“取反加一”。如:
(10000000)原转化为补码:
a.将0000000取反加一后,等于10000000
b.因为a得到的超过了七位,去掉a中10000000的1,再将题目中1给不上去。这里强调的是题目中的1和a中的1是不同的。
(2)补码的运算是连同符号位一起运算的。这里顺便提一下,二进制乘法的运算符号跟十进制乘法一样,符号位另算。