原码:最左侧一位表示符号,且0表示正数,1表示负数;二进制转换为十进制时,符号位只用于取正负号,不参与转化。
例如1个字节的二进制:0000 0001表示十进制的1;
0000 1010表示十进制的10;
1000 0001表示十进制的-1;
1000 1010表示十进制的-10;
根据原码的定义规则可知对于1个字节的有符号二进制原码的取值范围:[11111111]~[01111111]。即[-127]~[127]。比较特殊的是[10000000]表示-0,[00000000]表示+0,但是+0和-0是没有意义的,后面讲到补码时会说到如何解决+0和-0的问题。这样,对于1个字节的有符号二进制就可以表示256(2的8次方)个数值,包括+0和-0。
由以上可以看出,原码表示法简单直观,但是对于计算机来说,要使计算机能够自动识别最左侧的符号位,就需要更为复杂的硬件设计;
另外,在原码做减法运算时,根据数学上的加减乘除规则无法算出正确的值:
例如:1-1=1+(-1)=[00000001]原+[10000001]原=[10000010]原=-2
发现1-1不等于0。
总之,用原码作为计算机的存储方式不合适。
由上面可知,计算机不适合存储原码的原因是:1,无法识别二进制左侧的符号位;2,做减法时无法获取正确的结果。那么,有没有一种方式可以既不用考虑二进制左侧的符号位,又能够不做减法呢?
先来看一个时钟的例子,不考虑分和秒,生活中的钟表共1-12这12个刻度,假如此时时针指向6点钟方向,要想让它指向2点钟方向则有两种方式:1,逆时针移动4个刻度记作-4;2,顺时针移动8个刻度记作+8。可发现,-4的绝对值加8等于12。则我们称-4和8对于mod12互为补数。同样-3和9对于mod12互为补数。也就是说我们可以找到一个正数(8)代替负数(-4)用加法代替减法,且结果不变,如下图。
模拟时钟的逻辑,对于1个字节的二进制,共有256(2的8次方)种排列方式也就是可以表示256个数(类似于时钟的12个数),则其mod为256。
补码:正数的补码是其本身;负数的补码是符号位不变,其余各位取反后再加1,也就是反码加1。
例如:十进制数1的原码是0000 0001,其补码也是0000 0001,不把左侧第一位当成符号位直接转换成十进制仍是1。
十进制数10的原码是0000 1010,其补码也是0000 1010,不把左侧第一位当成符号位直接转换成十进制仍是10。
十进制-1的原码是1000 0001,则其补码是1111 1111,不把左侧第一位当成符号位直接转换成十进制是255,发现|-1|+255=256,说明-1的补码就是其补数。
十进制-10的原码是1000 1010,则其补码是1111 0110,不把左侧第一位当成符号位直接转换成十进制是246,发现|-10|+246=256,说明-10的补码就是其补数。
通过上面的例子,你会发现:1,补码不用考虑符号位;2,补码用加法代替了减法。这就很好的解决了原码的两个缺点。
看看下面-10转换为补码的流程:
1 0 0 0 1 0 1 0 -10
1 1 1 1 0 1 0 1 取反
+ 1 加1
由上可知,取反后原来是0的位变成了1,则弥补了原来的位权(eg:对于1000,1的位权是1*2的3次方,左侧第一个0的位权是0*2的2次方)为0的问题;原来是1的位变成了0其位权也变为0;符号位也不再表示符号位而是1*2的7次方,因此取反会转换为十进制为245,再加1变为246,246+|-10|=256。因此负数的补码是符号位不变,其余各位取反再加1。
有了以上的了解,对于1个字节的内存空间我们可以把数据在其中存储的情况想象成一个钟表的表盘:
对于上面的图片:
1:1个字节的内存空间其0和1的组合方式有256(2的8次方)种。
2:内圈的数值(无符号)是计算机中实际存储的补码对应的十进制。即不把符号位当成符号位而是当成普通的二进制位后的十进制数。其取值范围是0~255,共256个数,可以理解成 钟表的256个刻度,即mod为256,内圈(补码)的运算无符号无减法。
3:外圈的数值(有符号)是考虑符号位后补码对应的原码的十进制值,也是我们实际需要的值。其取值范围是-128~127,共256个数(很多人不理解为什么范围不是-127~127后面会解 释)。
4:已经知道原码的取值范围是[-127]~[127],包括+0(00000000)和-0(10000000);
因为1-1=1+(-1)=[0000 0001]原+[1000 0001]原=[0000 0001]补+[1111 1111]补=[1 0000 0000]补=[0000 0000]补=[0000 0000]原=+0,其中[1 0000 0000]补中的最高位1因为 溢出而被自动舍弃成[0000 0000]补。所以对于1个字节的内存我们可以用[0000 0000]补表示0(+0)。
又因为-128=-1-127=(-1)+(-127)=[1000 0001]原+[1111 1111]原=[1111 1111]补+[1000 0001]补=[1 1000 0000]补=[1000 0000]补,其中[1 1000 0000]补的最高1溢出而自动舍 弃成[1000 0000]补,因此对于1个字节的内存可以用[1000 0000]补来表示-128,需要说明的是[1000 0000]补只是拿出了原码中的-0的二进制形式用来表示-128的补码形式,因 此并没有原码和反码,视图通过[1000 0000]补获取其原码或反码的做法是错误的。
所以外圈取值范围是-128~127,而不是-127~127。
5:计算机中存储的是内圈的值(无符号无减法),而表现出来的却是对应的外圈的值,即,计算机中存储的是数的补码,每一个补码对应一个原码。
总结:采用补码存储的方式,计算机不需要考虑符号,也不需要做减法。
下面举1个例子验证一下上图是不是正确:
eg1:验证-127逆时针移动2为指向127。
因为-127的补数是129(10000001),-2的补数是254(11111110)
所以-127-2=(-127)+(-2)=[1111 1111]原+[1000 0010]原=[1000 0001]补+[1111 1110]补=[1 0111 1111]补=[0111 1111]补=[0111 1111]原=127。
另外:根据数据在内存中是以补码的形式存放的原理,可以很好理解,低字节向高字节扩展时,是带符号扩展的。
例如:两个字节的有符号数,扩展为4个字节时,若为负,则左侧所有字节全补1;若为正,则左侧所有字节全补0.
两个字节的无符号数扩展为4个字节时,则左侧全补0.