本章重要的知识点:
- 三种重要的数字编码表示
- 无符号编码
- 补码编码
- 浮点数编码
- 整数的表示虽然只能编码较小的范围,但却是精确的,而浮点数虽然可以编码较大的数值范围,但这种表示是近似的
- 字节顺序,大端vs小端
- 强制类型转换,底层的位值是不变的,只是改变了解释这些位的方式
- 两个正数相加会得出一个负数,而比较表达式
x<y
和x-y<0
会产生不同的结果,这是因为算术运算可能会溢出 - 补码非的两种方法
- IEEE浮点数表示,int类型的
12345
与float类型12345.0
的二进制表示之间的关联
1 字节顺序之大端与小端的区别
什么是字节顺序?
对于单字节的对象,没有字节顺序,因为无论怎么存储都是一个顺序,比如char。对于多字节对象,要保存在内存中,我们需要考虑在内存中如何排列这些字节,并且这些字节的地址是什么?
我们来看下大端和小端的定义:
- 大端:内存地址由低到高,内存按照由最高有效字节到最低有效字节的顺序存储对象。
- 小端:内存地址由低到高,内存按照由最低有效字节到最高有效字节的顺序存储对象。
假如有一个int类型的变量为0x01234567
,位于地址0x100
处,那么存储的地址范围为0x100~0x103
,大端和小端的表示方法如下:
其中0x01234567
的高位字节为0x01
,低位字节为0x67
。
为什么会有小端字节序呢?
计算机电路先处理低位字节,效率比较高。因为计算都是从低位开始的,所以,许多计算机内部采用小端字节序。但人类习惯大端字节序。比如网络传输和文件存储就是大端字节序。
2 计算补码非的两种方法
第一种就是常见的取反加一。对每一位求补,再对结果加一。比如整数值x
,计算表达式-x
和~x+1
的结果是一样的。在读csapp的时候发现了比较有意思的另外一种方法,首先将x的二进制表示处理,从右到左←的方向,找到第一个的1,然后再将这个1左边的位取反,就得到-x
一样的结果。
例子如下:
第一种方法
第二种方法
3 IEEE浮点表示方法
我们先来看一个有意思的现象,int类型的12345
和float类型的12345.0
,它们的十六进制表示分别为0x00003039
和0x4640e400
。
如图,它们对应的二进制用红色的方框圈出来的部分相同,这是为什么呢?学习了IEEE浮点表示方法,你就知道了。
IEEE浮点方法用下面的公式来表示一个数:
V
=
(
−
1
)
s
×
M
×
2
E
V = (−1)^s × M × 2^E
V=(−1)s×M×2E
-
s,sign表示符号位,决定V是整数还是负数,0为正,1为负。
- 一个单独的符号位s直接编码符号s
-
M,表示尾数,是一个二进制小数。
- n位小数字段frac编码尾数M,但编码出来的值还需要根据阶码字段的值是否等于0
frac = f n − 1 ⋯ f 1 f 0 \text { frac }=f_{n-1} \cdots f_{1} f_{0} frac =fn−1⋯f1f0
- n位小数字段frac编码尾数M,但编码出来的值还需要根据阶码字段的值是否等于0
-
E,exponent表示阶码,也就是指数部分。
- k位的阶码字段exp编码阶码E
exp = e k − 1 ⋯ e 1 e 0 \exp =e_{k-1} \cdots e_{1} e_{0} exp=ek−1⋯e1e0
- k位的阶码字段exp编码阶码E
在单精度浮点格式(32位),s、exp和frac字段分别为1位、8位和23位。而双精度浮点格式(64位),s、exp和frac字段分别为1位、11位和52位。
根据exp的值,将被编码的值分为三种不同的情况(以32位为例):
- 规格化的值
这是最常见的情况,exp的值不为0和255。在这种情况中,阶码的值E需要以偏置的形式来表示。
E
=
e
−
Bias
E=e-\operatorname{Bias}
E=e−Bias
B i a s = 2 k − 1 − 1 Bias=2^{k-1}-1 Bias=2k−1−1
在单精度中k为8,Bias为127。
小数字段frac用来表示小数值f。尾数定义为:
M
=
1
+
f
M=1+f
M=1+f
- 非规格化的值
当exp为0时,则为非规格化的值。在这种情况下,阶码值和尾数值如下:
E
=
1
−
Bias
E=1-\operatorname{Bias}
E=1−Bias
M = f M=f M=f
- 特殊值(无穷大,NaN)
当阶码值全为1时,小数域全为0表示无穷大,小数域为非零时,结果值为NaN(不是一个数的缩写)。
有了以上的理论,我们就可以尝试将整数值12345
转换为浮点数12345.0
(float类型)。12345的二进制表示为[11000000111001]
,将其转换为IEEE浮点格式
V
=
(
−
1
)
s
×
M
×
2
E
V=(-1)^{s} \times M \times 2^{E}
V=(−1)s×M×2E
将[11000000111001]
的二进制小数点左移动13位,创建12345的规格化表示:
12
,
345
=
1.100000011100
1
2
×
2
13
12,345=1.1000000111001_{2} \times 2^{13}
12,345=1.10000001110012×213
- 构造小数字段frac,丢弃开头的1,此时小数点后面有13位,因为frac一共有23位,所以需要在末尾增加10个0,从而生成frac字段
[110000001110010000000000]
。 - 构造阶码E,因为是规格化的值,E=e-Bias,故e=E+Bias=13+127=140,其二进制为
[10001100]
。 - 符号位,正数为0。
因此,我们就得到了12345.0的二进制浮点数表示[010001100110000001110010000000000]
。12345
的二进制整数表示为
[00000000000000000011000000111001]
。到此,我们就可以解释为什么12345
和12345.0
的二进制表示有部分是相同的,示意图如下:
参考:Computer Systems A Programmer’s Perspective THIRD EDITION