引子
这几天在看HashMap的源码,发现其源码中有一些非常巧妙的位运算,而位运算又牵涉到原码、反码、补码等计算机信息的表示。所以,我就在这里系统地总结一下这部分的知识,一来加深自己的记忆,而来希望可以帮到大家。
信息的表示
本节中的范例大多来自于《C++程序语言设计》这本书,各位可以去参考一下~
二进制
几乎所有的计算机都采用的是二进制数系。当数据被转换为二进制数后,计算机才能对其进行处理。当然,还有一些常见的进位数制我们也需要了解:
进 制 | 基数 | 进位原则 | 基本符号 |
---|---|---|---|
二进制 | 2 | 逢 2 进 1 | 0,1 |
八进制 | 8 | 逢 8 进 1 | 0,1,2,3,4,5,6,7 |
十进制 | 10 | 逢 10 进 1 | 0,1,2,3,4,5,6,7,8,9 |
十六进制 | 16 | 逢 16 进 1 | 0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F |
采用二进制码表示信息,有如下几个优点:
- 易于物理实现
具有两种稳定状态的物理器件是很多的:如门电路的导通与截止,电压的高与低。他们都恰好可以对应 0 和 1 两个符号。假如采用十进制,要制造具有10种稳定状态的物理电路,那是非常困难的。
- 二进制数运算简单
经过数学推导可以证明:对R进制的算数求和、求积规格各有 R(R+1)/2 种。如果采用十进制,就有 55 中求和与求积的运算规则;而二进制仅有3种,因此就简化了运算器等物理器件的设计。
- 机器可靠性高
由于电压的高低、电流的有无 等都是一种质的变化,两种状态分明。所以二进制编码的抗干扰能力强,鉴别信息的可靠性高。
- 通用性强
二进制编码不仅可以表示数值,而且还可以用于 非数值信息的编码。比如,布尔值 True、False 就可以分别和 1、0 对应,从而为计算机实现逻辑运算与判断提供了方便。
当然,二进制编码也有一些不足:它表示数的容量最小;表示同一个数,二进制比其他进制需要更多的位数。
进制转换
这一部分,我直接给出几个进制转化的例子,大家一看应该就能明白。
R进制转十进制
如下图,下面是最常见的 2进制 转 10进制:
如图,第二个例子是 带有小数的二进制数 转换为 十进制数。大家一看应该就能明白。
上面两个例子分别是 八进制、十六进制的数转换为 十进制数。这部分很简单,不多加阐述。
十进制转R进制
关于十进制数转换为R进制的数,这里分为两部分说明:整数的转换 与 小数的转换。
整数转换:
十进制的整数转换为R进制的整数,采用的是“除R取余法”。下面看例子:
也就是说,。再看一个例子,将
转换为 八进制数,采用除8取余:
也就是说, 。其他的进制转化也是类似,十进制转十六进制时,采用 除16取余 即可。这里就不多加介绍了。
小数转换:
十进制整数转换为R进制的整数时,采用的方法是“除R取余”。当需要转换的十进制数包含小数位数时,可以分为两部分处理:整数部分,处理方法同上,采用“除R取余”;小数部分,采用“乘R取整”。下面看例子:
将 转换为 二进制数:
如上图,我们将小数部分依次乘以基数,转换为R进制就乘以R。依次乘下去,直到小数部分为0,或者达到所需要的精度为止(小数部分可能永远都不为0。也就是说,十进制小数往往不能精确地转化为等值的R进制小数)。最终得到结果: 。
我们已经知道,也知道
,那也就是说:
。将整数部分和小数部分拼接起来即可。
二、八、十六进制间的转换
二、八以及十六进制 三种进制的数之间存在一些内在联系:每一位八进制数相当于3位二进制数(),每一位十六进制的数相当于4位二进制数(
)。
二进制数,从小数点开始,向左右分别按三(四)位为一个单元划分,每个单元单独转换成为一个八进制(十六进制)的数,就完成了二进制到八进制(十六进制)的转换。在转换时,位组的划分是以小数点为中心向两边延伸,中间的 0 不能省略,两头不够时可以补 0 。
八进制(十六进制)的每一位,分别独立转换成三位(四位)二进制数,除了左边的最高位,其他位如果不足三位(四位)的要用 0 来补足,按照由高位到低位的顺序写到一起,就是相应的二进制数。
下面看几个例子:
上图多看几遍,也是很容易理解的。
信息的存储
在计算机内部,各种信息都是以二进制编码形式进行存储。
信息存储的单位
信息存储的单位通常采用“位”、“字节”和“字”。
- 位:bit,度量数据的最小单位,表示1位二进制信息。
- 字节:Byte,一个字节由 8 位二进制数字组成(1 Byte = 8bit)。字节是信息存储中最常用的基本单位。计算机的存储器通常也是以多少字节来表示它的容量。
- 字:Word,字是位的组合,并作为一个独立的信息单位处理。字又称为“计算机字”,它的含义取决于机器的类型、字长以及使用者的要求。常用的固定字长有 8位、16位、32位等。
- 机器字长:在讨论信息单位时,还有一个与机器硬件指标有关的单位,这就是机器字长。机器字长一般是指参加运算的寄存器所含有的二进制数的位数,它代表了机器的精度,如 32位、64位等。
二进制数的编码表示
数值在计算机内是采用二进制编码表示。数有正负之分,一般情况下,在计算机中使用“0”表示正号,“1”表示负数。符号位放在数的最高位。例如,8位二进制数 它们在机器中可以表示为:
其中最左边一位代表符号位,连同数字本身一起作为一个数。
人们研究了符号数的多种二进制编码方法,其实质是对负数表示的不同编码。下面介绍一下几种常见的编码:原码、反码 和 补码。
注意:在下面的示例中,使用 一个字节存放一个整数,所有整数使用8位二进制码表示!
原码
将 符号 数字化为 0 或 1,数的绝对值与符号一起编码,这样的二进制码称为“原码”。如上面编码后的A、B,得到的二进制码都是原码。
对于一个整数,编码方式就是上面例子中编码A、B那样,用 0、1分别代表 正好、负号。例如:
对于一个带符号的纯小数,它的原码表示就是把小数点左边一位用作符号位。例如:
采用原码表示法时,编码简单直观,与原始值(真值)转换方便。但是也有一些缺点:
- 零(0)的表示不唯一。
- 另一个原因是,使用原码进行四则运算时,符号位需要单独处理,并且运算规则复杂。
反码
反码很少使用,这这里讲它主要是因为 反码是求补码的中间码。
对于整数而言,正数的反码与原码表示相同;负数反码的符号位与原码相同,仍用“1”表示,其余各位取反,0变成1,1变成0。
很显然,和原码一样,反码中,0的表示也不唯一。
对于纯小数而言,反码也类似,小数点左边的符号位不变,小数点右边的小数位取反。
补码
对于一个正数来说,其原码、反码、补码 表示相同。对于一个负数,其补码由该数反码最末位加 1 求得。
对于一个纯小数,反码的求法一样:
从上面的例子中可以看出,零(0)的补码表示是唯一的。采用补码表示的另一个好处就是,当数值信息参与算数运算时,采用补码的方式是最简便的:一是因为符号位可以作为数值参加运算,最后仍然可以得到正确的符号,无需单独处理;二是采用补码进行运算时,减法运算可以转化为加法运算,简化了硬件中的运算电路。正是由于这些优点,在计算机系统中,数值都采用补码来表示和存储。
需要注意的是,补码的运算结果仍然是补码。下面看两个例子:
例一:求 67 - 10 = ?
如上图,67 与 -10 的补码如图,则有:
计算结果记为R,也就是说,R的补码为 00111001。显然R是个正数,也就是说,R的原码也为 00111001。则R = 57。
例二:求10 - 67 = ?
如上图,我们计算出 +10 与 -67 的补码,然后计算:
结果记为R,则R的补码为 11000111,从符号位看,R是个负数。要计算结果值,则我们要将结果的补码转换为原码:只需要对补码再求补码,即可还原为原码。
计算得出,R的原码为 10111001,则 R = -57。
总结
技术更新换代太快,但是计算机总是围绕着一些基本理论在发展。所以,我们在疯狂学习新技术时,也应该好好掌握好这些基础知识。技术变化很快,然而万变不离其宗。
参考文档
1、《C++语言程序设计》第四版,郑莉·著。第一章:绪论
2、《Java编程思想》第四版,Bruce Eckel·著。第三章:操作符