之前学习的原码、反码、补码之间的转换,都是课本上教的,但是不知道为啥要那么转换,最近在看《编码》这本书,讲到加法器的实现,又看到了补码,就决定把这几个的关系研究研究
以下故事纯属虚构,为了直观简单所有运算都是按照4bit运算来讲解,4bit最多表示16个数,0~15 或者-8~7 所以别越界哦,下面开始讲故事 ~
很早以前有个路人甲发明了二进制,逢二进一,3就用(0011=0*2³ + 0*2² + 1*2¹ + 1*2º)表示,2就用(0010=0*2³ + 0*2² + 1*2¹ + 0*2º)来表示,那么负3和负2用啥来表示呢,突然想到手写的10进制中,负数是在前面加一条横杠比如-6、-2,所以想二进制也用加一条"横杠"来表示负数吧,所以在最左边是1(横杠)的就是负数,不是1的就是正数。
后来发明了计算机,由于计算机实现加法运算还算容易,实现减法非常复杂(会在另一篇文章讲解加法器),所以就只有加法器,那么减法运算只能转化成加法,减一个数等于加一个数的负数,然后计算机就用路人甲发明的表示负数的方法进行运算
(1) 2+3 = 0010 + 0011 = 0101 = 5 俩正数相加还是不错的 ~
(2) 3-2 = 3 + (-2) = 0011 + 1010 = 1101 = -5 what?咋是-5 ,仔细看发现,只要是一个正数一个负数相加,最左边肯定是1,那就肯定是负数了 ,不合理
(3) 2-3 = 2 + (-3) = 0010 + 1011 = 1101 = -5 what? 同上 ~
(4) -2 -3 = -2 + (-3) = 1010 +1011 = 0101 = 10101 因为符号位相加导致溢出,最高位溢出结果为0101 = 5
(5) 0 + 0 = 0000 + 0000 = 0, 0 - 0 = 0 + (-0) = 0000 + 1000 = 1000 = -0, -0 - 0 = -0 + (-0) = 1000 + 1000 = 0000 = +0 运算没错,只是有正负0而已
从上面可以看出,符号位参与运算是有很大问题的,导致运算结果不对,那么怎么才能不让符号位进行运算呢 ~ 很简单,路人乙想到了一个号方法
反码要产生了
路人乙想 :符号位的产生,就是为了表示负数的,如果我表示负数的时候不用符号位,用另一种方式,是不是可以呢,结果就想到了用相反数表示负数,比如3 = 0011,因为二进制非0即1,所以-3可以将3(0011)按位取反得到1100,用1100表示-3,啊呀,这样不就没有符号位了。“反码”就产生了,用相反数表示负数的编码方式叫反码,那路人甲原来发明的编码方式就叫负数的"值"等于正数的"值"所以叫原码,(但是这种表示方式在读取负数的值的时候是不方便的,比如给你个1100,你必须得转换成他对应的正数0011(3),才知道是0011的反码,是"负0011"(-3))
那原码和反码啥关系呢 原码表示3用0011 反码表示3也用0011 ,所以正数的原码等于反码,原码表示-3用1011,反码表示-3是1100 所以负数的反码等于原码非符号位取反
这是百度百科对反码的解释,不要把反码和相反数弄混了,反码专门就是给咱们程序员造的词 ~
接下来咱们看看没有符号位的反码进行运算,结果会怎么样呢, 注意结果也是反码,下面已经没有符号位这一说了
(1) 2+3 = 0010 + 0011 = 0101 = 5 正数和原码一样,所以结果也是对的
(2) 3-2 = 0011 - 0010 = 3 + (-2) = 0011 +1101 = 10000 最高位溢出结果为0000 = 0,应该是1结果是0
(3) 2-3 = 0010 - 0011 = 2 + (-3) = 0010 + 1100 = 1110(反码,是一个负数) = -0001(转成正数) = -1 可以
(4) -2 -3 = -2 + (-3) = 1101 +1100 = 11001 最高位溢出后是1001 = -6 应该是-6结果少了1变成-6了
(5) 0 + 0 = 0000 + 0000 = 0000 = +0
0 - 0 = 0 + (-0) = 0000 + 1111 = 1111 = -0,
-0 - 0 = -0 + (-0) = 1111 + 1111 = 1 1110 = 1110 这个不对了,不管是+0还是-0都不是
仔细观察可以发现以下规律
(1) 正数 + 正数 = 正数 ----没问题
(2) 正数 + 负数 = 正数------结果少1
(3) 负数 + 正数 = 负数-----没问题
(4) 负数 + 负数 = 负数------结果少1
(5) -0 + -0 是不对的 -----结果多1
补码要产生了
眼看路人乙的想法也行不通,路人丙看到上面的规律(1)发现全是正数加是没问题的,从(2)可以看出,如果负数的表现形式在之前反码的表现形式上再补个1,那么(2)就没问题了,结果发现(4)也变得没问题了。因为是是补了个1得来的,那就叫补码吧 ~ 下面咱们来运算一下看看
公式由之前的 (负数 = 正数各位取反) ----> (负数 = 正数各位取反 + 1)
(1) 2+3 = 0010 + 0011 = 0101 = 5 正数 + 正数是和反码没区别的,所以没问题
(2) 3-2 = 0011 - 0010 = 3 + (-2) = 0011 +(1101 + 1) = 0011 +1110 = 10001 最高位溢出结果为0001 = 1
(3) 2-3 = 0010 - 0011 = 2 + (-3) = 0010 + (1100 +1) = 0010 + 1101 = 1111 补码运算,得到的也是补码,那这个补码是个负数,对应的值是多少呢,根据公式 (负数 = 正数各位取反 + 1) 则1111-1 = 1110 再取反 是0001 = 1,所以1111是正1的补数,那就是负一
(4)-2 -3 = -2 + (-3) =(1101 + 1) + (1100 + 1) = 11011 = 最高位溢出后是1011 对应的正数是 5((0101)),所以是5的补码,那就是负5
哇,运算的问题终于解决了,但是补码只是用来运算的,他的负数表示太不直观,所以最终定的是,二进制的正负数还是用原码表示(直观,咱们开发者变成的时候还是用原码便是),但是运算的时候用补码运算(数值在计算中就是用补码存的,运算也是用补码)
补充
上面补码的产生说的是根据反码,找出来的规律 ~这是我自己编的。。。。。,但是补码运算确实是没问题的
下面说说正经的,为啥叫补码呢,可能就是有点互补的意思的,A+B = 固定的数。A和B是互补的。从搜索的资料来看,大部分说的是根据“模”时钟启发来的。比如时钟一共12个数,1到12点,那么2+10 = 12、4+8=12,那2和10,4和8都是互补的。比如现在指针指向12点,想让时钟指到 1点,可以顺时针+1 也可以逆时针 减11 ,也就是 -(12-1)。想让时钟指向10点,可以逆时针 -2 也可以顺势针 +10。结论就是
顺势针拨的数(也就是绝对值) + 逆时针拨的数(也就是绝对值) = 总的个数,两个数还是对于12说是互补
听起来有那么点道理,但是现在是中午十二点,我顺势针+1是下午1点,逆时针-11是凌晨1点,位置是一样,可他妈时间不一样啊 ~ 带着疑问运算一把看看,咱们就只运算那两个之前有问题的(2)和(3),首先咱们举例都是4bit进行运算的也就是 内存占用是这样的
口口口口
四个格子,能表示的数是2的4次方 = 16也就是0-15,再注意一点就是用这种方式代替负数就是不想有符号位,所以下面的运算中所有的数最高位不代表符号,是数值位
(2) 3-2 = 3 + (16 -2) = 3 + 14 = 0011 + 1110 = 10001 去掉溢出的最高位是0001 = 1
(4)-2 -3 = (16 - 2) + (16 - 3) = 14 + 13 = 1110 + 1101 = 11011 = 最高位溢出 1011 是11
啊呀,一看用"模"运算也不对啊 ~到底咋回事 ~ 突然想到我的疑问,下午一点和凌晨一点能一样吗? 答案是 不一样 !
接下来继续讲解怎么就对了
把上面的(2)(4)再说一下
(2) 3-2 = 3 + (16 -2) = 3 + 14 = 0011 + 1110 = 10001 去掉溢出的最高位是0001 = 1,咱们本来是-2结果变成了+14,虽然位置一样,但是多转了一圈啊。咱们再看右边 10001,溢出以后咱们把最高位丢弃了,只剩0001,那么丢弃的最高位代表的就是16,就是一圈啊,等号左边多加了一圈,右边减了一圈,所以没毛病 ~按照上面的公式 3-2 = 0011 + 1110 = 10001,那-2可以用1110代替,那-2就是2(0010)取反加一
(4)-2 -3 = (16 - 2) + (16 - 3) = 14 + 13 = 1110 + 1101 = 11011 = 最高位溢出 1011 是11(在这没符号位这一说)
那这个怎么解释呢?
左边多了两圈,右边溢出一个,少了一圈,那右边还有多的一圈,说明1011还是多了一圈的,所以虽然结果是1011(11),但是他是多了16的,所以实际应该是-5,1011代表的-5也就是正5取反 + 1
(5) -0 - 0 = -0 + (-0) = (16 -0) + (16 -0) = (1111 + 0001) + (1111 + 0001) = 1111 + 1111 + 0001 + 0001 = 1110 + 0010 = 1 0000 去掉最高位后是0000 是0,但是根据上面说的,0000应该还得减去一圈那就是0000代表的是-16
仅仅是表示而已,比如一个字节是8位,如果有符号的话表示-128 ~ 127 但是你用过-128 吗
总结一下:
(1)原码、反码、补码,是互相独立的,不存在依赖关系,只是之间的表示方法存在规律
(2)原码、反码、补码都是针对负数怎么表示研究出来的。正数的表示方法都是统一的,负数的数值和正数的数值表示方法一样的方式就是原码、负数是正数各位取反的表示方式就叫反码、负数是正数各位取反再补上个1的表示方式就叫补码
(3)原码的形式可以很直观的表示出负数,所以在书面书写时,一般用原码,比如编程,但是因为计算问题,计算机存储的时候都是存储的补码,计算机只管存储,计算,不在意是否是正数,负数。正负是由编译器和操作系统自己的规则决定的。