转自 http://blog.sina.com.cn/s/blog_9e1f85cd010136b9.html
学习中会遇到一些看似简单的问题,但是问题后面会引发多个疑问,一点一滴的积累才是最重要的;
不能够把简单问题彻底弄清楚,也就成不了高手,问大家两个简单问题:
计算机中为什么采用补码才存储数据?为什么8位二进制可以表示-128?
如果你能够在几秒钟内说出原因,那证明你的看书看的很仔细,如果不能够准确答出,那就要跟着我一起来回顾
刚上大学时学过的知识了,温故而知新这个道理其实很深奥。
原因:
1. 原码的不足:0的二异性,+0的原码 = 00000000(8位来算);-0的原码:10000000;
2. 用原码进行四则运算时,符号位需要单独处理,且运算规则复杂;
3. CPU中,一个最小的运算单元(三极管)就是一个与非门,所有的运算加、减、乘除都要转换成与非运算,
每次与非运算都要是耗时间的,采用补码,进行与非运算的次数会最小,提高运算效率;
使用原码会提高使用寄存器的次数,效率差;
4. 负数采用补码之后,可以使加减统一为加法运算;使符号位能与有效值部分一起参加运算,从而简化运算规则;
正是由于上述原码的不足,促使人们去寻找更好的存储方式:补码。
补码思想的一个好例子:
时钟问题:假如准确时间为6点,现在时间是8点,那么顺时针拨10个小时和逆时针拨3个小时的效果是一样的;
即(8 - 2) = (8 + 10) mod 12;在模运算中,一个数减去另一个数,可以转化为一个数加上另一个数的补数。
注:
正数的原码 = 正数的补码 = 正数的反码
负数的反码,除了最高位符号位不变,其他位取反;
负数的补码,除了最高位符号位不变,其他位取反,并在末位加1;
补码转为为原码:
如果补码为正数,则原码为其本身;
如果补码为负数,则除符号位,各位取反,末位加1;
错误例子:
1 + (-1) = 0;
但是从原码角度来计算:
+1000 0001
----------
结果为-2,显然不正确,证明用原码来计算是存在错误的。
其实,我们可以得到8位负数的补码,-1的补码为0xff,-2的补码为0xfe, ... ,-127的补码为1000 0001;
我们发现一个规律:补码随着负增值的变大,反而越来越小,到-127时变为1000 0001,
那么我们可以很快得出-128的补码为:1000 0000;即用-0来表示-128;
负数的补码随着负数的增长而变小这个规律是正确的,因为我们把值还原的话,正是在补码的基础上求反再加1;从而可以得到-1的补码0xff还原之后,除去符号位,变成了除了-0之外,最小的数1;而-128的补码0x00还原之后,得到的原码为:1 0000 0000,除去溢出的符号位,正好为0。
举例1(8位计算):
-1 + 3 = 2:
-1原码:1000 0001
-1反码:1111 1110
-1补码:1111 1111
即:0xff
3原码:0000 0011
3补码:0000 0011
-1 + 3表示:
+0000 0011
-----------
10000 0010
高位溢出,最后结果为0000 0010,转化为原码为正数+2,结果正确。
举例2(8位计算)
-2 - 3 = -5:
-2补码:1111 1110
-3补码:1111 1101
+1111 1101
-----------
11111 1011
高位溢出,得出结果为:1111 1011,再转化为原码为:10000101,结果为-5,正确。
深度挖掘:
char类型的范围为-128~127,占8位;
8位二进制数的原码和反码表示不了-128,只能表示-127~+127之间的数:
-127: 1111 1111
127: 0111 1111
只有补码才能表示-128:
-127 - 1 = -128
-127补码:1000 0001
-1补码:1111 1111
-127 - 1:
+1111 1111
-----------
11000 0000
高位舍去得:1000 0000,转化为原码为:1111 1111 + 1 = 1 0000 0000;
因为+0和-0都是0,有重复,所以在八位二进制中就把-0当作最小数-128用;
即1000 0000;
记住:在八位二进制中,为了区分+0和-0的区别,就把-0当作最小数-128用,即1000 0000;
那么-129至少需要9位才能存下来,因为没有一个特定的像-0这种特例来表示它,所以-128很幸运
可以用8位来存储。
-128 - 1 = -129:
+1111 1111
-----------
10111 1111
高位截取后变成了正数,很奇怪吧?结果为0x7f;
那首次溢出结果为0的数是哪一个呢?
答案是-256.
原因:
-255补码为:1 0000 0001
-1补码为:
相加结果为10 0000 0000,溢出后截断低8位得:0000 0000,原码为0.
很有意思吧?
有 另一种理解方式 我觉得更 靠谱一些。
转http://blog.chinaunix.net/uid-16249993-id-2750042.html
问是如何计算出来?是不是还是按:取相应正数的原码,最高位即符号位,取1,其余各位取反后
按我的理解(上学时学c语言就是这样理解的,老谭那本书上讲整数的取值范围那段就有)
+128的原值在8位中是表达不出来的。
应当从数学层面上去理解:
从数学上计算 256-128=128,
因为:256-128=256+(-128)的补码
而 256-128=128
所以 256+(-128)的补码=128
所以 (-128)的补码=256-128
=128
而从数学上, 128=1000 0000
故规定-128的补码为 1000 0000
不存在+128 8位原码及反码的形式,按常规算法是不对的。
注意:只是规定而已,下面还有原因。
8位二进制 的补码组合序列有
0000 0000 - 0111 1111 0 - +127
1000 0000 用来干啥好呢?(表示-0,不是的,-0的原码是1000 0000 补码是
1000 0001- 1111 1111 -1 - -127
全部状态为256个
原码 补码 值
0111 1111 0111 1111 127
0111 1110 0111 1110 126
......补码不断-1........
0000 0000 0000 0000 0
1000 0001 1111 1111 -1
1000 0010 1111 1110 -2
1000 0011 1111 1101 -3
......补码不断-1........
1111 1111 1000 0001 -127
无法表达 1000 0000 -128
于是就有了规定 1000 0000 定为 -128的补码
这种定法和上面数学层面的表述是一致的。
先将该负数取绝对值,再用二进制表示出这个绝对值 (不管符号位)
对该二进制数进行取反加一操作就得到负数的补码了
-128 绝对值是 128
128的二进制表示为:
1000 0000
取反
0111 1111
加1
1000 0000
这就是-128的补码
这种办法算出的结果符合“规定值”,规定而已。