[音乐] 前面一节课介绍了浮点数的各类运算,
包括加减乘除,四种基本运算的算法,
浮点数运算过程中,需要保留
需要考虑保留附加位,将中间结果转换为
最终结果是,需要考虑舍入,并进行溢出判断,
浮点数运算的一个最容易出错的情况是精度问题, 很容易由于精度问题导致运算结果不正确,
本讲将通过一些例子来说明浮点数运算存在的精度问题, Ariana
火箭爆炸的例子, 在1996年6月4号,Ariana5火箭初次航行,
在发射仅仅37秒钟以后,就偏离了飞行路线,
然后解体爆炸,火箭上载有价值5亿美元的通信卫星。
这个爆炸的原因是因为
在将一个64位浮点数转换成16位带符号整数的时候,
产生了溢出异常,一个64位的浮点数,
它的表示范围比16位带符号整数要大得多, 在转换的时候,由于这个原来的数
大,而转换以后的表述范围小, 所以呢,就很容易产生溢出,这个溢出的值实际上
是火箭的水平速度,Ariana5的火箭 它的水平速度比原来的Ariana4火箭
所能达到的这个水平速度高出了5倍, 而在设计Ariana4火箭软件的时候,
设计者确认这个水平速率 不会超过一个16位的整数,
所以他当时用的是一个16位的整数来表示, 火箭的水平速度的,但是在设计Ariana5的时候,
他们没有重新检查这部分,而是用了原来的设计,也就是用了16位的整数,
来表示水平的速率,而这时候它实际上已经比原来的这个速率提高了5倍,
在机器里面对于不同数据类型 之间的转换往往存在于一些不容易察觉的错误,
这种错误有的时候会带来一些比较重大的损失,编程的时候要非常小心。
第二个例子,我们来看一下爱国者导弹的定位错误问题,
在1991年2月25号,
海湾战争当中,美国在沙特阿拉伯达摩地区设置的这个爱国者导弹
拦截伊拉克的飞毛腿导弹的时候呢, 没有拦截住,使得这个伊拉克的这个飞毛腿导弹击中了
一个美国的军营,杀死了美军28名士兵, 这个原因就是爱国者导弹当中有一个系统时钟
这个系统时钟内的一个软件发生了一个错误, 这个软件错误的原因是因为浮点数的精度问题,
在爱国者导弹的系统当中,有一个内置的时钟,这个时钟是
计数器来实现的,能够每隔0.1秒计数一次,
那么这样的话,程序就用这个计数器的计数次数, 乘以0.1,乘出来的结果是这样,就是秒,
比如说假定现在计了10次数, 每一次是算0.1秒,就每0.1秒计数一次,
如果计了10次的话,那就意味着已经过去了 一秒钟,所以他就把这个计数次数和0.1
直接相乘,乘出来的结果作为这个时间, 以秒为单位的时间,那么这个0.1
在机器里面,它是用24位的定点二进制小数来表示的,
这个小数变量如果是x的话,那么这个x是一个24位的定点小数,
那么这个x它的这个机器数应该是什么呢? 0.1是一个十进制数,这个十进制数对应的
二进制的表示,它是一个无限循环0/1序列, 它等于0.00011
0011 0011 0011 一直循环下去的, 所以这个x用24位的
定点小数来表示的话,它就等于0.000 1100 1100 1100
1100 1100 这样的话一共是 24位,这个x很显然是0.1的近似表示,
因为它是有限字长的,它不能表示这个无限的0.1 这个近似表示和真正的0.1之间的
差应该是等于多少呢?0.1是等于0.000 然后是1100
1100 1100 1100 一直循环下去的,而x在
机器里面表示的是0,000,然后1100 1100
1100 一共24位,1、 2、 3、 4、 5、
6,四六24,24位 的小数,像这样的话,减下来的这个结果
实际上高位都是0,然后从这个地方开始
x是无法表示的,所以这个地方应该是减出来的差, 那么这个值实际上是等于0.1
乘上2的负20次方,也就是说这边是 1、
2、 3、 4, 四四16,再加上这边的3位
就是1、 2、 3、 4、 5、 6、 7、 8、 9、 10、 11、 12、 13、
14、 15、 16、 17、 18、 19、
20.到这后面 0.1,因此这个小数点如果是点在这的话,
那么就是2的负20次方,然后再乘上 0.1,那么这个值约等于9.54乘上10的
负8次方,那么这一个值 就是机器数和真正的0.1之间的误差,
由于这个误差导致了定位的错误, 当时这个爱国者导弹准备拦截
飞毛腿导弹之前,已经连续工作了100个小时, 飞毛腿导弹的速度大约是每秒2000米,
由于刚才讲的那个误差而导致的距离,误差应该是等于多少,我们可以算出来
100个小时相当于 对那个计数器计了这么多次,
就是100个小时,每小时60分钟,每分钟60秒,每秒
钟要计10次计数,100个小时一共计了这么多次。
每次的误差是刚才我们看到的9.54乘上10的负8次方, 所以,一共这么多次,总的误差
差了0.343秒,而0.343秒乘上这个速度
得到的距离的误差就是600多米,
以色列方面已经发现了这个问题, 也知会了美国陆军及爱国者计划办公室,
也就是这个软件的制造商,以色列方面建议重新启动爱国者导弹系统的电脑,作为
暂时解决方案,也就是说不用隔那么长时间一直在转,一直在转的话,它计数次数会
越来越多,然后每次都误差那么一点,计数次数多了以后,那么这个误差累积的越来越大,
当时是这么一个方案,但是美国陆军 方面不知道到底应该每次间隔多长时间启动一次系统,
1991年2月16号这个制造商向美国陆军提供了这个更新的软件,但是这个软件最终
却在飞毛腿导弹击中军营以后的一天才运抵部队,所以发生了这样的一件事情,
刚才我们讲的这个x,它是用24位的定点小数来表示的,
如果我们把这个x用浮点类型来表示的话,那个x的机器数 应该是什么呢?也就是说我们的0.1用
float型来表示的话,它的机器数应该是什么? 这时候真正的0.1和用
float表示的机器数之间的偏差又是多少? 在这样的偏差下面,最终的距离偏差是多少?我们可以算出来,
0.1刚才我们讲了它是一个无限的循环小数, 用 float型表示的话,我们先要转换成1.
什么什么乘上2的多少次方,很显然我们要把这个小数点
前面一位变成1的话,这个小数点向后移4位 然后变成1.10011
0011 0011 这种形式,小数点向后移了4位,相当于
乘上2的负4次方,因此x的机器数用float类型来表示的话
符号当然就是0,表示正数。
然后阶码部分实际上是负4 加127,那么实际上就是 127减4。
这个127就是7个1 1 2 3 4 5 6
7,7个1,然后再减4,就在这一位上面把1变成0,这个就是 阶码部分。
剩下来的尾数部分就是100 1100 1100 1100
1100 11这样子的100 1100 1100 11
这就是在机器里面,用float表示的0.1 很显然,float有效位数是24位
所以后面0.1循环的那个部分都被截断了
24位以后的有效位被截断了,所以它们之间是有误差的
就是机器里面表示的,用float表示的这个0.1和真正的0.1之间是有误差的
这个误差是这样子的,实际上也就是拿这个值和真正的0.1减
减下来的话,前面一共有这么多个0 一共是2的负24,也就是这小数点要移到
这个位置,这边一共有24位 1 2 3 4 5
6,四六24位 然后后面是0.000 1100
1100,这就是0.1 所以是2的负24次方乘0.1,这是它的误差。
就是每计数一次,误差这么多 最后算下来,偏差是43,比爱国者导弹系统的精确率高16倍
那么如果我们还是用32位数来表示,但是呢我们用的是定点小数 也就是用的是0.000
1100 1100 1100一直到这,一共是32位定点小数来表示0.1的话
它这个误差比float型的表示应该是大还是 小呢?很显然误差应该更小,也就是精度更高
因为这边是24个有效位数,然后再加上前面的这个的话
那么实际上,它这边的有效位数应该是 因为它这个是规格化的
表示还要加上这个三位,这三位的000,实际上已经是 取消了。
就是这三位的000是不表示的 因此它的有效位数,还可以24加3,就是27位
而这呢可以表示32位有效数字 所以这个上面有效数字没有下面的多
因此,用32位来表示的时候它的精确度应该更高 我们可以看到,实际上它是这个x就是用这个值来表示
它和0.1相减以后得到的这个 误差是这个值,这个值相当于2的30方乘以0.1
然后这个值再来算误差的话,算下来只有0.67米 上面我们举了两个例子
那么其中第二个例子呢,我们看到0.1。
因为在机器里面是没办法用 二进制精确表示的,所以它有误差
这个误差我们可以用尽量多的有效位数来表示0.1 这样的方式来提高精度。
那么前面我们讲过三种方式,一种是用24位的定点小数
一种呢是用32位的float类型,还有一种呢用32位的
定点小数,可以看出32位定点小数 它的精度是最高的,它比float的精度还要高64倍
在机器里面,用float表示数据的时候,实际上它的计算速度更慢
因为我们必须先要把那些数值,比如说刚才我们看到的那些 速度啊等等的这些都是要变成浮点数的格式
然后按浮点数的这个格式来进行相乘,很显然用float
数相乘,比直接用两个二进制数相乘要慢 又有尾数又有阶码,要分别进行计算
Ariana火箭和爱国者导弹前面我们讲的这两个例子给我们 带来一些启发。
首先第一个程序员应该对底层机器级的数据表示
和运算有深刻的理解,这样的话,我们编程序的时候才能知道哪个地方可能会出现问题
那么在计算机世界里面,经常是差之毫厘失之千里。
刚才我们看到每计数一次 看起来微不足道,差那么很小的,但是当累计很多次的时候
最后的结果会相差非常大。
另外一个启发 告诉我们不能遇到小数就用浮点数表示,像刚才那个0.1
我们如果不用浮点数表示,而直接用一个定点的小数来表示的话 它的效率可以更高。
像刚才那个例子就是一个整数 变量乘以一个确定的小数常量0.1,这时候我们可以
把0.1用一个确定的定点整数常量和整数
变量相乘,乘出来以后再通过移位的方式来确定小数点
这样的话,效率更高了
[音乐]