![]()
《夏洛特?zé)馈?/p>
撰文 | 烏其多
審校 | 劉六七
???????????????????????????????????????????????????????????????????????
眾所不周知,大部分編程語言都算不出0.1+0.2=0.3。
比如Javascript一通計(jì)算下的結(jié)果就為:0.30000000000000004!
![]()
cloud.tencent
你可能會(huì)說:“你先等會(huì)兒!”
然后打開windows上自帶的計(jì)算器,輸入“0.1+0.2”,然后拿著計(jì)算結(jié)果掄圓了準(zhǔn)備打我的小臉。
![]()
自制
其實(shí),計(jì)算器看似正確的結(jié)果,只是計(jì)算器把存在精度問題的最后幾位在顯示的時(shí)候舍入掉了而已。我們可以理解為這是一種“障眼法”。
![]()
自制
那問題到底出在哪兒?
要理解這個(gè)現(xiàn)象,得先搞明白計(jì)算機(jī)是怎么看待小數(shù)的。
我們平時(shí)用的是十進(jìn)制,但計(jì)算機(jī)只懂二進(jìn)制,也就是那一堆0和1。把十進(jìn)制小數(shù)轉(zhuǎn)成二進(jìn)制,用的是“乘2取整”法。以 0.1 為例:
![]()
自制
看出來了嗎?0.1 的二進(jìn)制是無限循環(huán)的:0.00011001100110011……(0011 循環(huán))。
同樣,0.2 的二進(jìn)制也是無限循環(huán):0.0011001100110011……(0011 循環(huán))。
看不出來也不要緊,我們只要記住:我們十進(jìn)制世界里大部分稀松平常的小數(shù),二進(jìn)制都是無法精準(zhǔn)表達(dá)的。
0.1 和 0.2在二進(jìn)制世界里,就是“無限循環(huán)小數(shù)”,也就如同十進(jìn)制無法精確表示 1/3 一樣。
除了0.1和0.2以外,π、√2等數(shù)學(xué)上的無理數(shù)(無限不循環(huán)小數(shù)),二進(jìn)制下更是超綱了,計(jì)算機(jī)同樣也是無法精確表示的。
弄懂了這個(gè)概念,我們?cè)賮砜从?jì)算機(jī)的存儲(chǔ)法則。
我們現(xiàn)在主流的編程語言,通用的存儲(chǔ)法基本都是“IEEE 754浮點(diǎn)數(shù)標(biāo)準(zhǔn)”,只要是使用了這個(gè)存儲(chǔ)標(biāo)準(zhǔn)的語言,比如JavaScript、C/C++、Java、以及曾號(hào)稱人人都應(yīng)該掌握的Python等等,都是無法準(zhǔn)確計(jì)算出“0.1+0.2=0.3”的。
![]()
自制
IEEE 754是由電氣與電子工程師協(xié)會(huì)(IEEE)于1985年制定的浮點(diǎn)運(yùn)算技術(shù)標(biāo)準(zhǔn)。
在這部“數(shù)字憲法”制定之前,各大計(jì)算機(jī)廠家都有自己的浮點(diǎn)數(shù)存儲(chǔ)法則,你不兼容我的,我不care你的,主打一個(gè)各論各的,造成了很多混亂。
IEEE 754標(biāo)準(zhǔn)就規(guī)定了浮點(diǎn)數(shù)在計(jì)算機(jī)中的存儲(chǔ)格式,常見的有32位單精度和64位雙精度。每個(gè)數(shù)被拆分為符號(hào)位、指數(shù)位和尾數(shù)位三部分,尾數(shù)位長(zhǎng)度是固定的(如雙精度為52位),那么遇到無限循環(huán)或者無限不循環(huán)小數(shù)的時(shí)候,怎么辦?那就是把尾數(shù)“一剪沒”了。
這樣截?cái)嗷蛏崛耄徒o0.1和0.2造成了存儲(chǔ)誤差。兩個(gè)近似值相加,誤差累積,就導(dǎo)致了0.1+0.2≠0.3的現(xiàn)象。
![]()
梅開二度、二度返場(chǎng)|《夏洛特?zé)馈?/strong>
你可能又得問:浮點(diǎn)數(shù)存儲(chǔ)為啥非得把尾數(shù)截了?計(jì)算機(jī)這么牛,全存了不行嗎?
答案很簡(jiǎn)單:不是不想,是做不到。
很多人以為計(jì)算機(jī)存儲(chǔ)和計(jì)算都是絕對(duì)精確的,其實(shí)這是個(gè)誤解。
存整數(shù)確實(shí)可以——比如數(shù)字 42,轉(zhuǎn)成二進(jìn)制 101010,放進(jìn)固定大小的空間里,只要空間夠大(不溢出),拿出來還是 42,分毫不差。就像把雞蛋放進(jìn)倉庫,拿出來還是那個(gè)雞蛋。
但問題是,數(shù)學(xué)世界里的數(shù)遠(yuǎn)不止如此。無限循環(huán)和無限不循環(huán)的數(shù),遠(yuǎn)遠(yuǎn)多于整數(shù)。
計(jì)算機(jī)的存儲(chǔ)空間是有限的,無論用多少位,都裝不下一個(gè)無限長(zhǎng)的二進(jìn)制串。所以,它只能截取前幾十位,后面的直接扔掉。
你可能又會(huì)想:那能不能把空間做得再大點(diǎn),存更多位?從幾十位變成幾百位也行啊!
理論上可以,但實(shí)際意義不大。
對(duì)絕大多數(shù)工程、圖形、科學(xué)計(jì)算來說,“IEEE 754浮點(diǎn)數(shù)存儲(chǔ)法”所用的 64 位雙精度(約 15-17 位有效數(shù)字)已經(jīng)足夠精確。
![]()
對(duì)不同領(lǐng)域來說,計(jì)算精度要求是不同的,天文學(xué)家能算準(zhǔn)光年就很牛了|網(wǎng)絡(luò)
如設(shè)計(jì)飛機(jī)、渲染電影、模擬天氣,沒人需要知道 π 的第10000位是什么(當(dāng)然,π能不能算到最后一位,在數(shù)學(xué)上意義重大,可能意味著數(shù)學(xué)大廈的地基比如微積分、極限理論被動(dòng)搖)。況且,無理數(shù)有無窮多個(gè),再大的空間也裝不完。
所以計(jì)算機(jī)選擇了一種“務(wù)實(shí)”的策略:用有限的存儲(chǔ),近似地表示所有的實(shí)數(shù)。專業(yè)的相關(guān)學(xué)科就叫做計(jì)算機(jī)數(shù)值分析,研究的就是“近似”的藝術(shù)。
對(duì)于任何一個(gè)數(shù)值計(jì)算問題,數(shù)值分析都會(huì)追問:誤差有多大?算法快不快?結(jié)果穩(wěn)不穩(wěn)?能不能算出來?只要這四個(gè)問題都能給出滿意的答案,那就是合格的計(jì)算結(jié)果。
回到 0.1 + 0.2。這不是程序的 bug,而是二進(jìn)制與有限存儲(chǔ)共同作用的必然結(jié)果。
理解這個(gè)“誤差”,就是數(shù)值分析教給我們的最重要一課:
認(rèn)識(shí)誤差,理解誤差,最終學(xué)會(huì)與誤差共存,并控制它。
????
參考資料:
[1]https://bbs.huaweicloud.com/blogs/287063
[2] https://www.puntoflotante.net/FLOATING-POINT-FORMAT-IEEE-754.htm
[3]https://www.h-schmidt.net/FloatConverter/IEEE754.html
原創(chuàng)文章版權(quán)歸微信公眾號(hào)
“把科學(xué)帶回家”所有
轉(zhuǎn)載請(qǐng)聯(lián)系:bd@wanwuweb.com
凡本公眾號(hào)轉(zhuǎn)載、引用的文章 、圖片、音頻、視頻文件等資料的版權(quán)歸版權(quán)所有人所有,因此產(chǎn)生相關(guān)后果,由版權(quán)所有人、原始發(fā)布者和內(nèi)容提供者承擔(dān),如有侵權(quán)請(qǐng)聯(lián)系刪除。
??
▲ 關(guān)注我們,點(diǎn)亮在看,分享一下?
特別聲明:以上內(nèi)容(如有圖片或視頻亦包括在內(nèi))為自媒體平臺(tái)“網(wǎng)易號(hào)”用戶上傳并發(fā)布,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。
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.