现代计算机数字表示
1 概述
现代计算机存储和处理信息以二值信号表示。二值信号可以很容易的被表示、存储和传输,例如可以表示为导线上的高电压、低电压。对二值信号进行存储和执行计算的电子电路非常简单可靠,制造商可以在一个单独的硅片上集成数百万甚至数十亿个这样的电路。
对于数字而言,有三种比较重要的表示:
- 无符号(
unsigned
)编码:对大于等于零的整数进行编码 - 补码(
two's-complement
)编码:对有符号的整数进行编码,包含正负数 - 浮点数(
floating-point
)编码:对实数进行编码表示,使用以2为基准的科学记数法方式
计算机的表示是使用有限数量的位来对一个数字进行编码,因此,当数字太大以至于不能表示时,运算结果就会溢出(overflow
)。
值得注意的是,整数的编码和浮点数的编码处理数字表示有限性的方式不一样:
- 浮点数编码可以对一个较大范围的值域进行编码,但是这种表示只是近似的,存在精度缺失
- 整数编码虽然只能编码一个范围较小的值域,但是是精确的表示,有效范围内不会存在精度缺失问题。
2 信息存储
信息在计算机的内存中,往往都是以二进制补码形式存在。对于如何在内存中方便的使用信息,需要两个规则:
- 信息的地址是什么?
- 如何对信息的字节进行排列?
对于被存储为连续字节的信息对象,对象的地址为所使用字节中最小的地址。
2.1 字节顺序
排列一个信息的字节有两个通用的规则:
- 大端法(
big endian
):信息的高有效字节存放在高地址,地有效字节存放在地址 - 小端法(
litter endian
):信息的地有效字节存放在高地址,高有效字节存放在低地址
例如,存在一个整型变量int x = n
,它的十六进制表示为0x12345678
。那么内存会给它分配四个字节的存储空间。假设地址值为0x100-0x103
。那么就0x100
就是低地址,0x103
就是高地址。
对于字节的最高有效字节和最低有效字节的顺序,是从左往右依次降低的。12
为最高有效字节,依次类推,78
就是最低有效字节。
对于大多数Intel
兼容机,都是采用小端模式;IBM
和Oracle
大多数机器则是大端模式;对于移动端来说,无论是Andriod
还是IOS
都是小端模式。
2.2 字符与字符串的表示
2.2.1 字符表示
C
或者C++
中,对字符采用某种标准编码进行表示,比较常用的有ASCII
字符码,就是使用1个字节的bit
位来对字符进行编码。
遗憾的是ASCII
编码仅仅适用于英文文档,对于一些特殊字符以及中文编码不支持。所以Unicode
联合会整理修订了支持广泛语言编码的基本编码-Unicode统一字符集
,使用32位来表示字符。
2.2.2 字符串的表示
在C
或者C++
中,字符串被编码为一个以NULL
('\0'
,值为0)结尾的字符数组。每个字符由标准编码表示。
2.3 整数表示
整数用bit位来表示也有两种不同的方式。一种是只能表示非负整数;另外一种是能够表示负整数、零以及正整数。无论哪种方式,在内存中都是以补码形式存在。
2.3.1 无符号整数编码
无符号数总是大于等于零,所以无符号数的补码就是它二进制原码本身:
unsigned int num = 10;
// 原码 int类型占用32bit
0000 0000 0000 0000 0000 0000 0000 1010
// 补码 内存中存放的是补码
0000 0000 0000 0000 0000 0000 0000 1010
无符号整数的所有bit
位都是数字有效位。
2.3.2 有符号整数编码
对于有符号的整数,将其转换为二进制之后,最高位(最左边)bit
位代表符号位,不参与数据表示。其中0表示正数,1表示负数。
对于补码:
- 正数补码 = 正数二进制原码
- 负数补码 = 负数二进制原码除了符号位,其余位取反 + 1
//无符号整数 正数
int a = 3;
// 3的原码。第一位是符号位,3是正数,所以是0
0000 0000 0000 0000 0000 0000 0000 0011
// 3的补码
0000 0000 0000 0000 0000 0000 0000 0011
//无符号整数 符数
int a = -3;
// -3的原码 第一位符号位,3是负数 所以是1
1000 0000 0000 0000 0000 0000 0000 0011
// 然后是反码,除了符号位,其余位取反
1111 1111 1111 1111 1111 1111 1111 1100
// 最后是补码:反码 + 1,这就是负3在内存中的形式
1111 1111 1111 1111 1111 1111 1111 1101
2.4 实数表示
在计算机中,实数表示方法与整数的表示方法是不同的。
实数的二进制组成有三个部分符号位、阶码位和尾数位。毋庸置疑,最高有效位是符号位。次高有效位之后是阶码位,它的长度取决于精度范围,单精度浮点型的精度只有8位;双精度浮点型有11位阶码位。阶码位之后,剩余的就是尾数了。
例如,存在一个单精度浮点型数据9.65
,将其转换为在内存中的二进制表示。其中float
占用4
个字节。也就是32
位。所以(以下顺序是从右往左):
- 符号位:占用
1
位,第31
位 - 阶码:次高有效位开始,占用
8
位。第23~30
位 - 尾数:阶码结束之后就是尾数,占用23位,第
0~22
位。
下面开始转换:
- 9.65是正数,所以符号位是0
- 确定阶码,首先将9.65转为二进制表示:
(1001.1010 0110 0110 0110 0110)
b;再转为二进制指数表示形式:(1.0011010 0110 0110 0110 0110
* 103)b;阶码 = 指数部分+127,阶码 = 3 + 127 = 130,使用八位二进制数表示(1000 0010)
b。 - 取步骤
2
中,转为二进制指数形式之后的小数部分作为尾数001 1010 0110 0110 0110 0110
,不足23
位的,低位使用0
补充
所以,完整的数据为:(0 1000 0010 001 1010 0110 0110 0110 0110)
b。这样组合只是为了清楚的看到各部分之间组成。
重新按照4个二进制一组(0100 0001 0001 1010 0110 0110 0110 0110)
b,使用十六进制表示:(41 1A 66 66)
hex。
如过使用工具查看内存的话,大端法就是(41 1A 66 66)
,小端法则是:(66 66 1A 41)
。
多数电脑都是小端法。,下面就是再小端法表示的电脑上运行结果。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。