前两天看到书里的小实例,涉及到移位运算符,还和同事研究了半天,现在深入总结一下。
注:大部分内容来自网络
原码
最高位为符号位,“0”表示正,“1”表示负,其余位表示数值的大小
反码
正数的反码与其原码相同;负数的反码是对其原码按位取反,但符号位除外。
补码
正数的补码与其原码相同;负数的补码是在其反码的末位加1。
吐槽,这里的计算方式都很特别啊。
问:负数在计算机中如何表示?
计算机内部采用2的补码(Two's Complement)表示负数。(即平常大家都叫补码)
问:在计算机中怎么计算减法?
全部换成补码形式相加计算。
不断的提问,找资料,解决问题……后来,我发现对于原补码的世界观都被刷新了……
以下摘自知乎https://www.zhihu.com/question/20458542/answer/40759880的Simon Cao回答
第一步,就像练北冥神功要先散功一样,先把你心中对原码,反码,补码的一套认识全部忘掉
第二步,正式开讲
要说清这个问题,需要颠覆你对补码的理解
首先灌输一个新的概念叫,模
什么是“模”,想象日常使用的钟表,它可以显示0~12点的时间,假设现在是2点钟,请用手动拨动时针的方式将时间减4小时,你会怎么做?
有两种方式:
逆时针将时针拨4小时
顺时针将时针拨8(12-4)小时
这里要讲的是第二种方式,为什么顺时针拨12-4也可以达到和正常思维的第一种方式一样的位置。
12就是模。
同样的,如果是十进制的两位数,80-10 和 80+90在不考虑百位数的基础上都是70。这里的90就是100-10得来的,这种情况下100就是模
模就好比是一个极限,在它的范围内,两个相加等于模的数互为补数,还是举100的例子
90和10, 55和45,68和32,互为补数
在模的范围内做减法,可以将“X-Y”的减法变更为“X+Y的补数“的加法,当然前提是不考虑百位数
思考题,上面举的例子是大数减小数,那么如果是小数减大数会怎么样呢?
如果是10-80,结果应该是-70,但如果按照10+(100-80),结果是30。
而很明显-70和30不是一回事,这里也没有百位数的问题,这种情况应该怎么破?
当初的那些先贤们想出来的办法很简单,就是把这两个数直接划上等号,正好顺便解决了负数的表达方式。再来仔细看看这两个数的关系:-70绝对值的补数就正好是30
所以在计算机中,负数的表达方式就是它绝对值的补数
但是问题又来了,看起来这个解决方式很完美了,但别忘了,30他已经代表了正数的30了,现在又要用来代表负数的-70,谁知道它出现的时候到底是代表哪个数?
为了解决这个问题,需要给这套规则划定一个范围,原来是0~99的正数,现在既然要用部分正数来代替负数了,那就要规定一个范围来使得一个数只代表一个含义,正好一人一半,0~49这个区间就代表正数,50~99的区间就用来代表各自补数的负值,例:98就代表-2
第三步,现在回到二进制的计算机世界
8位二进制数一共可以表示2的8次方,256个数,即0~255 (别忘了0也要占一位的),他们的极限就是256,即256是8位二进制数的模 ,应该不难理解吧,同上十进制的两位数0~99的模是100。
还是用二进制来说明清楚,8位二进制能表示的数的极限是
1 1 1 1 1 1 1 1, 就是255,在这基础上加0 0 0 0 0 0 0 1,出现了进一位 即 1 0 0 0 0 0 0 0 0
这个1 0 0 0 0 0 0 0 0就是8位二进制数的模,256
同样按照第二步讲的逻辑,一半的数0~127,代表其正数本身,另一半的数 128~255,代表其补数的负值,即“-1~-128”的区间。
而 “X-Y”的减法 就用 “X+Y的补数”的加法来表示,完美! 唯一需要注意的事情是任何计算的输入值和输出结果值都需要严格遵守-128~127的范围,一旦溢出就会报错。
这也就是我们在编程里强调的为什么 byte+byte还得是byte,int+int还得是int,数据溢出问题也是每一个程序员都需要注意的问题。
这样一说是不是可以理解-128的补码是怎么来的了吧? 他就是256-|-128|=128
二进制的128是不是就是1 0 0 0 0 0 0 0 ?
最终问题,那书和老师为什么要用原码,反码来讲补码 ?
空穴来风,未必无因
那是因为计算机就是这样求负数的补码的,我们在键盘上敲一个负数的时候,计算机要把它用补码的形式存储下来,还记得上面我们讲的补码是怎么来的吗?
模-绝对值,这是不是个减法公式?但计算机没有减法逻辑,我们费了那么大的劲搞了一套补码的规则就是为了用加法来替代减法,但为了实现这么套规则,却跨不过一个坎,就是把负数计算成补码仍然是需要减法逻辑的。怎么办呢,那些伟大的先贤们 (膜拜)就想出了这么个办法:
首位不变,其余位取反后,再加一
下面是吐槽
不知道是哪个书呆子教书,照搬了机器的逻辑,把取反加一的方法当做补码的计算逻辑就这么教下来了。搞笑的是,还保留了补码这个名字,照理说这种教法应该叫 取反加一码 更合理,你还补什么啊?
不仅如此,还搞出了个首位符号位的说法,弄出了个正0负0,还用负0来充当-128,真是不把人弄疯不罢休啊!!
哈哈,确实有意思啊。为此我也找资料论证他的观点,发现补码确实很有意思啊。
参考资料:原码、反码和补码的教学探讨