计算机对实数的理解股配资网站,就像狗对π的理解一样肤浅。
—— DeepSeek
撰文 | 姜洋(中国科学院理论物理研究所 2022级博士研究生)
飞来横祸
1991年2月25日,第一次海湾战争正如火如荼地进行着。当天晚上20时40分左右,突如其来的爆炸声划破了沙特达摩地区宁静的夜空——伊拉克发射的\"飞毛腿\"导弹精确命中美军位于达兰的空军基地。霎时间,原本秩序井然的军营化作一片火海,哀嚎遍野。
美军在化作废墟的军营中搜寻生还者
当时美军为了防范伊拉克的导弹袭击,早已在重要军事据点部署了以\"爱国者\"导弹为核心的拦截系统。不过在这场致命的袭击中,号称先进的\"爱国者\"系统忠实地充当了“吃瓜”群众的角色,对来袭的导弹毫无反应。最终,袭击导致28名美军士兵丧生,97人受伤,这成为海湾战争期间美军最惨重的单次伤亡事件。事后的调查显示,这场灾难的罪魁祸首是一个看似微不足道的数字计时器精度问题。
浮点运算的“迷惑”
实数在计算机中的表示,也就是浮点数,是科学计算操作的基本对象。可以想见,只具备有限存储空间的计算机永远也不能精确地表示不可数的实数集合。就像上文提到的爱国者,它的计时器只有24位精度。在连续运行100小时后,系统已经累计出了的0.34秒的误差。对于以6倍音速飞行的导弹而言,这相当于700米的定位偏差,足以让整个防御系统形同虚设。
浮点数表示的标准化肇始于上世纪80年代产生的IEEE754规范。随着体系架构的演进,近三十年来计算机工业完成了从32位至64位系统的变革。现代计算机系统已经普遍支持64位双精度浮点数,但这绝不意味着使用者可以忽视数值精度背后的复杂性。数值计算的艺术就是与误差共舞的艺术,\"舞技\"不好的表演者会在意想不到之处付出惨痛的代价……
读者对浮点数运算的性质了解多少呢?猜一猜下面这个表达式的结果:
>>> 0.1 + 0.1 == 0.2
试着在计算机上运行一下,返回结果为True,看起来一切正常。然而,很容易构造一个可能让人小吃一惊的案例:
>>> 0.1 + 0.2 == 0.3
返回的结果为False。事实上,双精度浮点计算下 的结果是
>>> 0.30000000000000004
浮点数的表示原理
人类使用十进制系统的原因很可能是我们拥有十根手指。但是在构造一个机器系统时,只有两种状态的比特位是最容易实现的。所以对于计算机,二进制表示是更自然的方式。在表示数时,十进制的位拥有10的若干次幂的权重。类似地,二进制(比特)位带有2的若干次幂权重。下面的一个从二进制转换至十进制的案例很好地说明了这一点:
1.1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 [1001...] * 2^(-4)--------------------------------------------------------------------------------------1.1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1010 * 2^(-4)
1.1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1010 * 2^(-4)+ 1.1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1010 * 2^(-4)--------------------------------------------------------------------------------11.0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0100 * 2^(-4)= 1.1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1010 * 2^(-3)
为什么0.1+0.2≠0.3
1.0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 * 2^(-2)
0.1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1101 * 2^(-3)
然后进行加法和舍入:
1.1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1010 * 2^(-3)+ 0.1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1101 * 2^(-3)--------------------------------------------------------------------------------10.0110 0110 0110 0110 0110 0110 0110 0110 0110 0110 0110 0110 0111 * 2^(-3)= 1.0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0100 * 2^(-2)
爱国者导弹
“爱国者”导弹系统内置了一个用于计时的钟——每0.1秒进行一次更新。与IEEE规则不同,这个运算是通过24位固定小数点寄存器实现的——在不采用科学计数法的情况下直接存储0.1的二进制表示。按理说,与0.1最接近的计算机近似表示是它的过剩值:
0.00011001100110011001100[1100...]-------------------------------0.00011001100110011001101
可是“爱国者”系统莫名其妙地使用了直接截断的不足近似值:
事实上,在2月11日,“爱国者”导弹项目组在分析以色列军方提供的系统日志时已经发现了这个软件错误:当系统连续工作8小时以上时,定位目标就会偏离正常位置的20%。他们在21日向所有使用“爱国者”的部队发出了消息:在“长时间”启动“爱国者“系统的情况下,射程就会发⽣偏离, 导致追踪⽬标的失败。也许是项目组觉得军方肯定不会让导弹系统的运行时间长到无法成功追踪目标,他们在发出的通告中完全没有解释这个“长时间”究竟是多长时间...
2月26日,也就是事故发生后的第二天,修复后的软件被运抵了达兰空军基地。
结语
从现在的视角来看,\"爱国者\"导弹的悲剧并不是源于什么深奥的技术谜题,但是简单的bug依然可以让人们付出惨痛的代价。人类认识世界、改造世界的步伐就是以这样一种蜿蜒曲折的方式前进着。浮点运算像是布满暗礁的海域,理解了潮汐规律的人才能安全航行。在数字化浪潮席卷一切的今天,每个0与1的抉择都可能成为命运的转折点。我们无法彻底消除误差,但每一次跌倒后的反思都将让我们在下一次跌倒前走得更稳、更远——只要我们永不停止从错误中学习的脚步。
参考文献
[1] Randal E. Bryant, David R. O’Hallaron.Computer Systems: A Programmer’s Perspective (3rd edition). Pearson. 2015.
[2] ⾦钟河,叶蕾蕾译.致命Bug,软件缺陷的灾难与启⽰. 人民邮电出版社. 2016.
[3]Wikipedia https://en.wikipedia.org/wiki/Iraqi_ballistic_missile_attacks_on_Saudi_Arabia.
本文经授权转载自微信公众号“中国科学院理论物理研究所”,原题目为《Doctor Curious 66:浮点数表示之殇:被背叛的爱国者》。
特 别 提 示
1. 进入『返朴』微信公众号底部菜单“精品专栏“,可查阅不同主题系列科普文章。
2. 『返朴』提供按月检索文章功能。关注公众号,回复四位数组成的年份+月份,如“1903”,可获取2019年3月的文章索引,以此类推。
景盛配资提示:文章来自网络,不代表本站观点。