計算機對實數的理解,就像狗對π的理解一樣膚淺。
—— 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月的文章索引,以此類推。
特別聲明:以上內容(如有圖片或視頻亦包括在內)為自媒體平臺“網易號”用戶上傳并發布,本平臺僅提供信息存儲服務。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.