原码、反码、补码之来龙去脉
2011-10-18 17:36
225 查看
1. 为什么偏向用十六进制?
16进制数逢16进1,2进制逢2进1。2^4=16。
16进制进一位,恰好对应二进制进四位(十六进制10 = 二进制1 0000, 十六进制100 = 二进制1 0000 0000)。
从而,16进制数(如: 2A9),可以按位依次转成二进制: 2(0010)A(1010)9(1001)。每一位对应二进制四位。
即: 2A9 = 0010 1010 1001
故,当看到十六进制数 fffffff3 时,就能立即方便地反应出其二进制的数值了。
后面会在数值前标明进制说明:
二进制: (binary) 0000 0011
八进制: (octal) 325
十进制: (decimal) 289
十六进制: (hex) ffff fff3
2. 数据在内存中存储的方式
int i = 3;
以 16 位系统为例,i 在内存中占两个字节,结构如下: (binary) 0000 0000 0000 0011
为了方便,我们设系统为8位的,int 只占一个字节。即 int i = 3 的存储结构为: (binary) 0000 0011
3. 溢出与"模"的概念
从上可以看出,int 占一个字节的话,能储存的数的范围是 (binary) 0000 0000 -> (binary) 1111 1111,或者说 (decimal) 0 -> (decimal) 255
如果两个数想加,超过了这个范围,那么就会"溢出"。
例如,在这个存储中, (decimal) 4 + (decimal) 254 = ?
从二进制的角度看:
(binary) 0000 0100 = (decimal) 4
(binary) 1111 1110 = (decimal) 254
------------------------------------- +
(binary) 1 0000 0010 = (decimal) 258
因为只能存储8位,溢出的那个 1 就不得不被丢掉啦! 于是就只剩下 (binary) 0000 0010 了。
也就是说:(decimal) 4 + (decimal) 254 = (decimal) 2 (丢弃溢出丢出来的奇怪结果!这可是后面能进行负数运算的根源所在呢!)
丢掉的那个溢出数,即 (binary) 1 0000 0000 = (decimal) 256,就被称为 "模"。
"模"可以理解为存储空间的长度(从 0 到 255,长度正好是 256)。当两个数的和超过了存储空间的长度时,我们就取其对模的余数(如:258 mod 256 = 2)。
4. 为什么要用补码?
按前面假设,int 只占一个字节,即8位存储结构。很明显,这里只能存储指定范围的正数,且只能进行加减运算(结果也只能属于这个范围,溢出的就丢掉)。
负数怎么办呢?
显然,计算机是不认识负数的。负数也可以理解为减运算。
根据前面的丢弃溢出的运算,我们有:
(decimal) 4 + (decimal) 254 = (decimal) 2
而显然的,我们还有:
(decimal) 4 - (decimal) 2 = (decimal) 2
也就是说,在丢弃溢出的运算条件下:
+ (decimal) 254 = - (decimal) 2
哈哈,是不是说 + 254 = - 2 啊?!
不错,在这种丢弃溢出的算法中,是可以这样的。(可以理解为钟表往前拨8小时(+8),与向后拨4小时(-4),是一样的结果)
也就是说,在存储范围内,如果 a + b = "模",那么,在丢弃溢出的运算条件下: + a = - b;- a = + b
那么,我们就可以直接用 + 254 表示 - 2 啦!(因为它们在运算中的效果是一样的嘛!)
从而 (binary) 1111 1110,既可以表示 + 254, 也可以表示 -2 喽!
可是,怎么让计算机知道,我要的值是 - 2,而不是 + 254 呢?
在前面再加个标志嘛。比如,加个 0 表示我们要取的是 + 254,加个 1 表示我们要取的是 -2。
可是我们的存储就只有8位唉! 最后只好将存储中第一位的值拿出来作为符号位(0表示是正数,1表示是负数),剩下的7位用来存储数值,如下:
0 000 0011
第一位 0 是符号位。结果为: 符号位 = 0,值 = (binary) 000 0011
嗯,原先8位存储结构,能存放数值为 0 => 255,现在为了支持负数,第一位用来做符号位,于是能表示的数值范围变成了 - 128 => + 127(取 1 000 0000 = - 128)。
显然,现在数值的"模"也变成了 128。与前面相同运算,与 - 2 对等的正数也变为 + 126 了。
于是,根据 - 2 = + 126,将 - 2 写成二进制:
(1). 符号保留在符号位,即,符号位 = 1 (0表示是正数,1表示是负数)
(2). 数值则直接用 + 126,即数值为 111 1110
结果:- 2 = 1 111 1110
而正数的二进制表示则简单、直观得多: + 126 = 0 111 1110
这样,只需将8位存储结构拿出第一位来表示正/负符号,我们就可以存储正/负数,并进行加减运算了(负数用对应的正数表示,反正运算结果是一样的;而减运算就是加负数嘛!)。
原码:
对任一个数,将其转换为二进制。然后在前面按"0表示是正数,1表示是负数"加一个符号位,就是原码啦!如:
(decimal) 2 的原码 = (binary) 0 000 0010
(decimal) -2 的原码 = (binary) 1 000 0010
反码:
对正数,等于其原码。
(decimal) 2 的反码 = (binary) 0 000 0010
对负数,顾名思义,就是对原码的数值的部分取反。如:
(decimal) -2 的反码 = (binary) 1 111 1101
补码:
对正数,等于其原码。
(decimal) 2 的反码 = (binary) 0 000 0001
对负数,等于反码再加1。
(decimal) -2 的反码 = (binary) 1 111 1110
明白了吧? 这不正是前面我们用 + 126 表示出来的 - 2 的值嘛!
是的,根据前面的分析,与负数相对等的那个正数,它们绝对值的和正好等于"模"(可以理解为钟表往前拨8小时(+8),与向后拨4小时(-4),是一样的效果)。对一个二进制数来讲,先取反,再加1,再加上原数,正好是一个和为"模"的过程,反映到十进制上就是通过-2找+126的过程。
所以说,负数在计算机中是用补码表示的。
对于零而言,(binary) 0 000 0000 的补码为 (binary) 1 000 0000,所以就取 (binary) 1 000 0000 为 -128。所以,负数的表示范围为: - 128 => + 127。
16进制数逢16进1,2进制逢2进1。2^4=16。
16进制进一位,恰好对应二进制进四位(十六进制10 = 二进制1 0000, 十六进制100 = 二进制1 0000 0000)。
从而,16进制数(如: 2A9),可以按位依次转成二进制: 2(0010)A(1010)9(1001)。每一位对应二进制四位。
即: 2A9 = 0010 1010 1001
故,当看到十六进制数 fffffff3 时,就能立即方便地反应出其二进制的数值了。
后面会在数值前标明进制说明:
二进制: (binary) 0000 0011
八进制: (octal) 325
十进制: (decimal) 289
十六进制: (hex) ffff fff3
2. 数据在内存中存储的方式
int i = 3;
以 16 位系统为例,i 在内存中占两个字节,结构如下: (binary) 0000 0000 0000 0011
为了方便,我们设系统为8位的,int 只占一个字节。即 int i = 3 的存储结构为: (binary) 0000 0011
3. 溢出与"模"的概念
从上可以看出,int 占一个字节的话,能储存的数的范围是 (binary) 0000 0000 -> (binary) 1111 1111,或者说 (decimal) 0 -> (decimal) 255
如果两个数想加,超过了这个范围,那么就会"溢出"。
例如,在这个存储中, (decimal) 4 + (decimal) 254 = ?
从二进制的角度看:
(binary) 0000 0100 = (decimal) 4
(binary) 1111 1110 = (decimal) 254
------------------------------------- +
(binary) 1 0000 0010 = (decimal) 258
因为只能存储8位,溢出的那个 1 就不得不被丢掉啦! 于是就只剩下 (binary) 0000 0010 了。
也就是说:(decimal) 4 + (decimal) 254 = (decimal) 2 (丢弃溢出丢出来的奇怪结果!这可是后面能进行负数运算的根源所在呢!)
丢掉的那个溢出数,即 (binary) 1 0000 0000 = (decimal) 256,就被称为 "模"。
"模"可以理解为存储空间的长度(从 0 到 255,长度正好是 256)。当两个数的和超过了存储空间的长度时,我们就取其对模的余数(如:258 mod 256 = 2)。
4. 为什么要用补码?
按前面假设,int 只占一个字节,即8位存储结构。很明显,这里只能存储指定范围的正数,且只能进行加减运算(结果也只能属于这个范围,溢出的就丢掉)。
负数怎么办呢?
显然,计算机是不认识负数的。负数也可以理解为减运算。
根据前面的丢弃溢出的运算,我们有:
(decimal) 4 + (decimal) 254 = (decimal) 2
而显然的,我们还有:
(decimal) 4 - (decimal) 2 = (decimal) 2
也就是说,在丢弃溢出的运算条件下:
+ (decimal) 254 = - (decimal) 2
哈哈,是不是说 + 254 = - 2 啊?!
不错,在这种丢弃溢出的算法中,是可以这样的。(可以理解为钟表往前拨8小时(+8),与向后拨4小时(-4),是一样的结果)
也就是说,在存储范围内,如果 a + b = "模",那么,在丢弃溢出的运算条件下: + a = - b;- a = + b
那么,我们就可以直接用 + 254 表示 - 2 啦!(因为它们在运算中的效果是一样的嘛!)
从而 (binary) 1111 1110,既可以表示 + 254, 也可以表示 -2 喽!
可是,怎么让计算机知道,我要的值是 - 2,而不是 + 254 呢?
在前面再加个标志嘛。比如,加个 0 表示我们要取的是 + 254,加个 1 表示我们要取的是 -2。
可是我们的存储就只有8位唉! 最后只好将存储中第一位的值拿出来作为符号位(0表示是正数,1表示是负数),剩下的7位用来存储数值,如下:
0 000 0011
第一位 0 是符号位。结果为: 符号位 = 0,值 = (binary) 000 0011
嗯,原先8位存储结构,能存放数值为 0 => 255,现在为了支持负数,第一位用来做符号位,于是能表示的数值范围变成了 - 128 => + 127(取 1 000 0000 = - 128)。
显然,现在数值的"模"也变成了 128。与前面相同运算,与 - 2 对等的正数也变为 + 126 了。
于是,根据 - 2 = + 126,将 - 2 写成二进制:
(1). 符号保留在符号位,即,符号位 = 1 (0表示是正数,1表示是负数)
(2). 数值则直接用 + 126,即数值为 111 1110
结果:- 2 = 1 111 1110
而正数的二进制表示则简单、直观得多: + 126 = 0 111 1110
这样,只需将8位存储结构拿出第一位来表示正/负符号,我们就可以存储正/负数,并进行加减运算了(负数用对应的正数表示,反正运算结果是一样的;而减运算就是加负数嘛!)。
原码:
对任一个数,将其转换为二进制。然后在前面按"0表示是正数,1表示是负数"加一个符号位,就是原码啦!如:
(decimal) 2 的原码 = (binary) 0 000 0010
(decimal) -2 的原码 = (binary) 1 000 0010
反码:
对正数,等于其原码。
(decimal) 2 的反码 = (binary) 0 000 0010
对负数,顾名思义,就是对原码的数值的部分取反。如:
(decimal) -2 的反码 = (binary) 1 111 1101
补码:
对正数,等于其原码。
(decimal) 2 的反码 = (binary) 0 000 0001
对负数,等于反码再加1。
(decimal) -2 的反码 = (binary) 1 111 1110
明白了吧? 这不正是前面我们用 + 126 表示出来的 - 2 的值嘛!
是的,根据前面的分析,与负数相对等的那个正数,它们绝对值的和正好等于"模"(可以理解为钟表往前拨8小时(+8),与向后拨4小时(-4),是一样的效果)。对一个二进制数来讲,先取反,再加1,再加上原数,正好是一个和为"模"的过程,反映到十进制上就是通过-2找+126的过程。
所以说,负数在计算机中是用补码表示的。
对于零而言,(binary) 0 000 0000 的补码为 (binary) 1 000 0000,所以就取 (binary) 1 000 0000 为 -128。所以,负数的表示范围为: - 128 => + 127。