深入理解计算机系统
本文为《深入理解计算机系统》第二章 信息的表示和处理的笔记,本章主要介绍了无符号数、补码数(整数)和浮点数在计算机中的编码方式和运算规则。了解底层数据的存储原理能让我们写出更安全的程序,避免犯如下这样严重的错误
《深入理解计算机系统》原书电子版下载:
深入理解计算机系统(原书第三版).pdf
信息的表示和处理
数字表示:
- 无符号编码:大于0或等于0的数字
- 补码编码:有符号整数,可以为正或者为负的数字
- 浮点数编码:实数(以2为基数的科学计数法)
整数编码数值范围较小,但精确;浮点数编码数值范围较大,但只是近似。
一、信息存储
字节:最小的可寻址的内存单位
虚拟内存:机器级程序将内存视为一个非常大的字节数组,称为虚拟内存,
每个字节都有一个数字(地址)标识,地址的集合称为虚拟内存空间
1、寻址和字节顺序
对于跨越字节的程序对象(int,float等),必须有两个规则:
-
这个对象的地址是?
对象的地址为所使用字节中最小的地址。 -
在内存中如何排列这些字节?
大端法:从最高有效字节到最低有效字节的顺序存储
小端法:从最低有效字节到最高有效字节的顺序存储假设int类型的变量x,位于地址0x100处,它的十六进制值为
0x01234567
。地址范围0x100~0x103的字节顺序(高位字节值为01,低位字节值为67):
大端法 | ||||
---|---|---|---|---|
…… | 0x100 | 0x101 | 0x102 | 0x103 |
值 | 01 | 23 | 45 | 67 |
小端法 | ||||
---|---|---|---|---|
…… | 0x100 | 0x101 | 0x102 | 0x103 |
值 | 67 | 45 | 23 | 01 |
贴一段印象深刻的代码,作用是按字节打印跨字节对象的地址:
2、布尔代数(按位运算)
设P和Q:
- 非(~):P为1时,~P为0;反之为1
- 与(&):P和Q都为1时,P&Q为1
- 或(|):P和Q任意一个为1,P|Q为1
- 异或(^):P为1Q为0或P为0Q为1时,P^Q为1
3、逻辑代数
逻辑代数中,非零为TRUE,0为FALSE,返回0或1分别表示FALSE和TRUE;
逻辑运算与布尔代数运算规则近似,但功能完全不同,举例说明:
- 非(!):!54 结果为0,!0结果为1
- 与(&&):12 || 0 结果为0
- 或(||):0 || 8 结果为1
逻辑短路:如果第一个参数能确定表达式的结果,就不会对第二个参数求值
4、移位运算
-
左移(<<):向左移动k位,丢弃最高的k位,并在右端补k个0
例如值为 [01100011] 的数x:x<<4的结果为 [00110000]
-
右移(>>):右移分为逻辑右移和算术右移:
- 逻辑右移:在左端补k个0 ;值为[10010101]的数x,x>>4结果为[00001001]
- 算术右移:在左端补k个最高有效位 ;值为[10010101]的数x,x>>4结果为[11111001]
有符号数一般使用算术右移,无符号数使用逻辑右移
Java里没有无符号数,因此明确定义x>>>k为逻辑右移
二、整数表示
数据类型的取值范围由他能分配的字节数决定
1、整型数据类型:
32位整型数据类型取值范围:
64位整型数据类型取值范围:
1.1、无符号数(非负数)编码
每个整数都能映射为一个唯一的长度为ω的位模式(十进制–>二进制)
例如二进制(1111)2能映射为唯一的十进制表示(15)10 :
(1111)2 = 1
×
\times
× 23 + 1
×
\times
× 22 + 1
×
\times
× 21 + 1
×
\times
× 20= 8 + 4 + 2 + 1 =(15)10
从位到无符号数的映射:
1.2、补码(整数)编码
将最高有效位解释为负权,负权为1代表该数符号位为负,他的权重为-2ω-1
例如二进制(1111)2 = -1
×
\times
× 23 + 1
×
\times
× 22 + 1
×
\times
× 21 + 1
×
\times
× 20= -8 + 4 + 2 + 1 =(-1)10
位到补码数的映射:
补码能表示的范围:
2、有符号数和无符号数的转换
有符号数和无符号数相互转换规则:保持位值不变,改变这些位的解释方式
例如:有符号数(1011)和无符号数(1011)的值分别为-5和11,相同的位值,解释方式不同值也不同。
在16位机器上,补码数-12345强制类型转换为无符号数后,值为53191
在32位机器上,补码数-1强转为无符号数后,值变为4294967295
3、C语言中的隐式类型转换
在C语言中执行一个运算时,如果一个是有符号一个是无符号的,c会隐式地将有符号转换为无符号数:对关系运算会导致非直观的结果。
例如表达式 -1 < 0U
,因为第二个数是无符号的,第一个数会被转隐式转换为无符号数,因此这个表达式等价于4294967295U<0U
如下图,-1明显小于0,但输出的是大于:
4、扩展一个数字的位表示
扩展一个数的位数,同时保持数值不变
0扩展:在数的开头添加0
符号扩展:在数的开头添加符号位
无符号数使用0扩展,有符号数使用符号扩展,值才不会改变:
- 无符号数
101
扩展至四位0101
,还是十进制值5 - 有符号数
101
扩展至四位1101
,还是十进制值-3
5、截断一个数字的位表示
- 无符号数:1001截断最高一位变为001,值从9变为1
- 有符号数:1001截断最高一位变为001,值从-7变为1
上面的这些错误、溢出大部分都是因为使用无符号数造成的,但合理使用无符号数能节省一半的内存空间
三、整数运算
1、无符号加法
两个无符号数相加,如果值超过能表示的范围(2ω),会对他进行截取
从位上很容易理解:
考虑四位无符号 1011 和 0110 相加,值10001超出范围,截取后为0001。即11 + 6 的值由17变成1
截取规则:对ω位的数,进行模2ω运算:17%16 = 1,与上面一致
映射规则:
无符号加法图示:
2、补码加法
两个补码数相加,如果值超出范围(大于2ω-1或小于-2ω-1),会进行截取操作
同样从位上理解:
四位补码数1010和1100相加,值10110超出范围,截取后为0110,-6+-4的值从-10变为6
映射规则:
补码加法图示:
3、补码的非
对十进制取反操作很容易:
在范围内的数x,除最小值取反还是自己外,其余数取反都为-x例如对9取反值为-9
补码非的位级表示:
- 对每一位求补,再对结果加一:
[0111] → [1000] → [1001]( 7 → -8 → -7)
- 假设k是最右边的的1的位置,对k左边的所有位取反:
[0111] → [1001] ( 7 → -7)
4、无符号乘法
将一个无符号数截断为ω位,等价于该值模2ω
5、补码乘法
ω位的有符号数相乘,结果可能需要2ω才能表示。
将一个补码数截断为ω位相当于先计算该值模2ω,再把无符号数转换为补码
例如:
6、乘以常数
整数乘法指令相当慢,需要十多个时钟周期。而其他运算(加减、位级运算和移位)只需要一个时钟周期。所以把乘法优化为移位和加法的组合可以提高运行效率。
左移k位等价于乘2的k次幂:
例如:四位的数[1011],左移2位得到[101100],值由11变为44,相当于乘22
乘一个较大的数可以把它分解为2的次幂相加的形式:
x * 14 (14 = 23 + 22 + 21)等价于(x<<3)+(x<<2)+(x<<1)
7、除以2的幂
除以2的幂也可以用移位运算来实现,无符号数使用逻辑右移,补码数使用算术右移
无符号数的移位总是舍入到0的结果,与整数除法的规则一致。
补码数的移位会向下舍入,例如右移4位会把-771.25向下舍入位-772,解决办法是在移位前加一个偏置量:
偏置量的值为:2k-1(2k为除数)
偏置量的证明:
最后思考
四、浮点数
1、二进制小数
浮点数的表示:小数点左边数字的权是正幂,得到整数值;小数点右边数字的权是负幂,得到小数值。
例如:十进制数12.34可以表示为1 * 101+ 2 * 100+ 3 * 10-1+ 4 * 10-2
二进制小数点向左移动一位相当于这个数被2除,向右移一位相当于乘2。
在有限长度编码下,小数的二进制表示法只能表示被写成 x * 2y的数,其他值只能近似表示
例如数字1/5可以用十进制0.20精确表示,但并不能精确表示为一个二进制小数,只能近似的表示它:
2、IEEE浮点数表示
IEEE标准规定,用: (-1)S × M × 2E 的形式表示一个浮点数。
符号:S决定这数是负数(S=1)还是正数(S=0)
阶码:E的作用是对浮点数加权,权重是2的E次幂
尾数,M是一个二进制小数,M的范围大于等于1,小于2
看图好理解些,如下图,十进制数 5.5 即二进制的 101.1
①把小数点移到最高位后,变成一个大于1小于2的数,即尾数M M=1.011
②移动了2位,即阶码E E=2
③这个数就变为(-1)0 × 1.011 × 22的形式
④在内存里存储只需要存储符号、阶码、尾数就等于存储了这个值
因为指数可以为负,为了便于计算,规定阶码都先加上1023(2^10-1)
符号(1位) | 阶码(11位) | 尾数(52位) |
---|---|---|
0 | 10000000001(1023+2) | 01100000…… |
该例为64位的(双精度浮点数Double)格式,符号、阶码、尾数分别占1位、11位和52位)
在32位的(单精度浮点数Float)格式下,符号、阶码、尾数分别占1位、8位和23位
五、小结