二进制小数 就像十进制小数一样,二进制的小数点左边是正幂,右边是负幂,只不过幂底由10变成2。小数点可以移动,左移表示除以2,右移表示乘以2。 同十进制小数只能精确表示10的幂组成的数一样,二进制小数也只能精确表示由2的幂组成的数。同十进制小数一样,如果想要更接近不能精确表示的数,只有通过增加位数来
二进制小数
就像十进制小数一样,二进制的小数点左边是正幂,右边是负幂,只不过幂底由10变成2。小数点可以移动,左移表示除以2,右移表示乘以2。
同十进制小数只能精确表示10的幂组成的数一样,二进制小数也只能精确表示由2的幂组成的数。同十进制小数一样,如果想要更接近不能精确表示的数,只有通过增加位数来逐渐近似。
2.45 二进制小数转换
小数值 |
二进制表示 |
十进制表示 |
1/8 |
0.001 |
0.125 |
3/4 |
0.11 |
0.75 |
25/16 |
1.1001 |
1.5625 |
43/16 |
10.1011 |
2.6875 |
9/8 |
1.001 |
1.125 |
47/8 |
101.111 |
5.875 |
51/16 |
11.0011 |
3.1875 |
2.46 导弹计时错误
- 0.1的二进制表示是 0.000110011[0011],程序中的x是 0.00011001100110011001100。将两者对齐之后互相减一下:
0.00011001100110011001100110011001100110011.....
0.00011001100110011001100
得到结果是 0.00000000000000000000000[1100]
- 近似的10进制值是2的24次幂分之一加上2的25次幂分之一。
- 100个小时之后,系统计数的次数运行了100*3600*10 = 3600000次。误差大概是0.343秒。
- 2000*343 = 686米
IEEE浮点数表示
浮点数分为单精度和双精度。单精度32位,双精度64位。其构成是:
- 单精度: 1位符号位,8位指数位,23位尾数
- 双精度: 1位符号位,11位指数位,52位尾数
符号位如果=0,表示是一个正数,是1,表示是一个负数。
指数位是最关键的位置,这个部分的数值,决定了这个浮点数的三种情况。以单精度为例:
- 指数位的原始二进制数是00000001——11111110,即不等于无符号的的上下限0和255,这表示规格化数,可以理解成表示一般的小数。这种情况下,指数等于无符号数的大小减去偏置数,尾数的部分自动变成1.尾数。
- 指数位的原始二进制数是00000000,这个叫做非规格化数,此时尾数的部分就是.尾数,不再加上1。此时阶码固定等于1-偏置数。非规格化用来表示接近与0的数值。
- 指数位的原始二进制数是11111111,这个是特殊值。如果此时尾数全部为0,则表示0,有+0和-0之分。如果尾数不为0,表示NaN。
明白了小数的表示之后,就可以发现很有趣的现象,在都是正数的情况下,正好是按照无符号数逐渐增大来排列的。而负数排列的时候,就是按照降序来排列。
练习题2.47 练习IEEE浮点数
1个符号位,2个阶码位,2个尾数位的浮点数,偏置显然是1.
二进制 |
e 无符号数 |
E 减去偏置后的实际值 |
阶数实际值 |
f 表面尾数 |
M 实际尾数 |
2E*M 尾数实际值 |
V 实际分数值 |
十进制 |
0 00 00 |
0 |
0 |
1 |
0/4 |
0/4 |
0 |
0 |
0.0 |
0 00 01 |
0 |
0 |
1 |
1/4 |
1/4 |
1/4 |
1/4 |
0.25 |
0 00 10 |
0 |
0 |
1 |
1/2 |
1/2 |
1/2 |
1/2 |
0.5 |
0 00 11 |
0 |
0 |
1 |
3/4 |
3/4 |
3/4 |
3/4 |
0.75 |
0 01 00 |
1 |
0 |
1 |
0 |
1 |
1 |
1 |
1.0 |
0 01 01 |
1 |
0 |
1 |
1/4 |
5/4 |
5/4 |
5/4 |
1.25 |
0 01 10 |
1 |
0 |
1 |
1/2 |
3/2 |
3/2 |
3/2 |
1.5 |
0 01 11 |
1 |
0 |
1 |
3/4 |
7/4 |
7/4 |
7/4 |
1.75 |
0 10 00 |
2 |
1 |
2 |
0 |
1 |
2 |
2 |
2.0 |
0 10 01 |
2 |
1 |
2 |
1/4 |
5/4 |
5/2 |
5/2 |
2.5 |
0 10 10 |
2 |
1 |
2 |
1/2 |
3/2 |
3 |
3 |
3.0 |
0 10 11 |
2 |
1 |
2 |
3/4 |
7/4 |
7/2 |
7/2 |
3.5 |
浮点数在越靠近0的地方越密集,在远离0的地方变稀疏。
通过实际操练,可以知道常见的浮点数的最大和最小范围。比如单精度浮点数,E最低取到1-127 = -126,尾数部分最低就是1,则就是2的-23次方,合起来最小数字就是2的-149次方。
最大数字则是E取到254,指数=254-127 = 127,尾数部分全是1,此时代表1.(23个1) = 2 - 2的-23次方。双精度的也就可知了。
练习 2.48 解释练习2.6中的情况
0x00359141写成二进制是:0000 0000 0011 0101 1001 0001 0100 0001。
0x4A564504写成二进制是:0100 1010 0101 0110 0100 0101 0000 0100。
移动一下找匹配的部分:
00000000001101011001000101000001
01001010010101100100010100000100
现在就可以来解释这种匹配不是巧合的原因了。0x00359141的二进制,不补到32位的表示是11 0101 1001 0001 0100 0001
。将其改成规格化数,则是1.1 0101 1001 0001 0100 0001
。
这个时候可以看到,小数点往右移动了21位,单精度情况下,阶码的值应该等于127+21 = 148,即二进制的10010100。尾数部分注意由于是非规格化数,开头的1可以去掉,剩下21位在末尾补2个0。按照顺序写出的浮点数是:
0 10010100 1 0101 1001 0001 0100 0001 00
这就解释了为什么看起来像是整数的除了第一位1之外的部分被包含在浮点数中间一样。而且很显然,如果整数的二进制超过23位,精度就会有损失了。
2.49 不能精确由浮点数表示的正整数
从上边一个题目可以看出来,如果一个数字有N位二进制数,按照规格化分割之后第一位是1,后边有N-1位二进制表示,第一位1可以省略。而浮点数格式能存放n位,即 N-1 <= n。
即N<=n+1,所以当N = n+2的时候,如果尾数不是0,就会出现问题。因此最小的正整数,就是2的n+1次方再加1
。
可以用很简单的方法推导,如果n=4,则5位2进制数都可以放下,因为最高1位去掉之后,还有4位可以精确表示。但是到了6位二进制,如果是100000,依然可以精确表示,因为没有尾数。
但是一旦有最后一位,比如100001,写成1.00001*2的5次方的时候,最后一个1就会丢失,所以对于4位尾数的浮点数,100001 = 33就无法精确表示了。
对于单精度浮点数,2的24次方+1这个整数就无法表示了。这个数字就是16777216+1 = 16777217
舍入方向
这个其实就是对尾数的操作的不同方式。
默认的舍入方式是偶数舍入,也就是找最近的舍入。对于不在两个整数之前的浮点数,就是离其最近的整数。而对在两个数字正中间的整数,则按照让舍入位的最后一位为0来进行舍入。
2.50 练习:
- 10.010 ,由于这个数正好在要舍入的10.0的一半,所以直接舍入到10.0
- 10.011 ,这个数字直接舍入到10.1
- 10.110 ,这个数在要舍入的10.1的一半,所以要舍入到10.1的最低位是0,也就是11.0
- 11.001 ,这个数不是一半,直接舍入到11.0
2.51 练习:
已经知道x = 0.0001 1001 1001 1001 1001 100,如果要舍入到23位小数。那就先来多写几位看看:
0.0001 1001 1001 1001 1001 100 1100 1100 1,很显然结果是 0.0001 1001 1001 1001 1001 101,实际上这个数要比0.1要大
用这个数字减去0.1,由于是舍入到23位小数,那么结果就是23位小数之后的无限循环部分
2.52 练习:
格式A |
格式B |
位 |
值 |
位 |
值 |
011 0000 |
1 |
0111 000 |
1 |
101 1110 |
阶码 = 5 -3 = 2,结果是15/8*2的2次方 = 15/2 = 7.5 |
由于格式B的小数也是3位,可以放得下这三位,阶码也为2即可,所以是1001 111 |
7.5 |
010 1001 |
2-3 = -1 值是 (1+(1/2+1/16))/2 = 25/32 |
由于放不下这么多小数位,所以向0舍入,结果是0110 100 |
3/4 |
110 1111 |
8*(1+1/2+1/4+1/8+1/16) = 31/2 |
首先变换相同的阶码是10,则前四位是1010,如果不舍入 应该是1010 1111,然后舍入到1011 000 |
16 |
000 0001 |
这个是非规格化,所以阶码=1-3 = -2,结果是1/4*1/16 = 1/64 |
这个要注意,最小的是2的6次方分之1,而阶码4位可以直接表示出-6,所以就是0001 000 |
1/64 |
浮点运算
浮点加法有个最大的问题,就是不具有结合性,大数会把小的数字吃掉,这是因为舍入的原因。乘法也一样。
C语言在支持IEEE 754的机器上,float对应单精度浮点,double对应双精度浮点。此外在X86上,long double对应80位长度的浮点数,但这个不能移植。
所以很多时候,会直接定义浮点数。
2.53 完成下列定义
#define POS_INFINITY
,正无穷,通过分析可以知道,双精度正无穷最高位是0,阶码的11位全是1,然后是52位的0。写成十六进制就是0x7FF0000000000000
#define NEG_INFINITY
,负无穷,通过分析可以知道,12个1,然后是52位的0。写成十六进制就是0xFFF0000000000000
#define NEG_ZERO
,符号位是1,0是非规格化数,所以是0x8000000000000000
实际的答案采用了溢出的方式,双精度最大的表示大概是1.8*10的308次方,所以可以使用让正无穷等于 1e400之类溢出即可。然后负无穷等于负的正无穷,然后用1/负无穷就得到负0。
类型转换的时候,int之前已经知道,转换成float可以转换,但是会被舍入。而int和float转成double都没有问题,因为double的位数太多,覆盖了int的32位和float的23位,所以不会损失精度。
double 转 float就可能溢出或者损失精度。而从float或者double转成int,由于不带小数,值会向0舍入,也可能会溢出,因为可表示范围要大于int。与intel兼容的处理器,在浮点数转换成int如果找不到对应的值时,会生成最小的int值即-2147483648.
但是在实际实验的时候,发现溢出的double转int的时候, (int)1e100会得到int的正上限即2147483647, (unsigned int) 1e190则会得到-1, 因为是转成了111111...
2.54 类型转换
x == (int)(double)x
, int转double 不会损失精度,在转会double,由于在int范围内,所以没有问题
x == (int)(float)x
, 从上次做过的题目可以知道,int转float可能会被舍入,最小的正整数是2的24次方+1 = 16,777,2167
d == (double)(float)d
, 这个简单, 任何一个大于float上限的double或者阶码转换的之后精度超过24位的double转成float都会损失精度
f == (float)(double)f
, 这个是没有问题的.
f == -(-f)
, 这个也没有问题,浮点数的符号转换就是转换最高位.
1.0/2 == 1/2.0
, 都是先转换然后除,不会有问题
d * d >= 0.0
这个也成立
(f + d) - f == d
这个不一定.有可能f很大而d很小,f吞掉d,最后得到0.
第二章顺利看完了,记得一年多前刚买这书的时候初看还感觉和看天书一样. 现在第二章也顺利搞定了. 再把后面的家庭作业做做.
下一章是程序的机器级表示也就是汇编, 今天下午整体过了一遍, 感觉用心一点应该还是可以看懂的. 加油吧. 今天王爽的汇编语言也到了, 结合着一起看.