深入理解JavaScript系列:為什么03-0.2不等於0.1


五一宅家看書,所以接着更新一篇文章。

今天講一下為什么03-0.2不等於0.1這個問題。

有點標題黨的味道,在JavaScript中,當你試着對小數進行加減運算時,有時候會發現某個結果並非我們所想的那樣,就比如標題中所說的為什么我用0.3去減0.2卻得不到0.1?

當我碰到這個問題的時候我一下子也不知道問題到底出在哪,但他實實在在的就給出了一個0.09999999999999998的結論。

其實這個問題的本質原因就是在於計算機對浮點數的處理上,對於計算機的運算方法和數據的表示處理等內容是很大的一塊內容,而且這塊內容只要是計算機專業的同學在大學期間都已經接觸到了而且很可能在你的期末考試試卷上就有這樣的一道相關的題目是檢測你這個知識點的。所以今天在朋友圈發了一條動態,內容是不好好學習的下場就是你連為什么03-0.2不等於0.1都不知道。

下面從淺顯易懂的角度去講述這個問題。

1.計算機中如何表示數據?

  計算機是個機器,內部所有數據和指令都是以01為基礎來實現。也就是計算機的認知水平僅僅局限於二進制,而我們習慣於十進制計數規則,所以這中間就牽扯到一個各種數制之間的轉換的問題。數制有二進制、八進制、十進制和十六進制,當然只要你高興,搞個三進制五進制也不是不可以,但是這幾個進制是我們約定俗成和被廣泛使用的。

2.各數制間如何進行轉換?

  真的不怎么想寫這個問題,因為能回答這個問題的答案在你的課本里邊早就有很詳細且很全面的講解,如果你浮躁到只想知道結果而自己不懂卻連課本都不想翻的地步,那么這篇文章其實也沒必要看了。

  但是為了與下面內容更好的連貫性,這里還是簡單講一下怎么從十進制轉換到二進制的過程。

  十進制轉換為二進制:

  比如11(10)這個十進制數,我們轉換成二進制為11(10)=8+2+1=11=23+21+20=1000+10+1=1011(2);如果看不懂這個轉換,那你去翻課本吧,這里就不列那些公式什么的了。 

  二進制轉換為十進制:

  二進制轉換為十進制也就是十進制轉換為二進制的一個逆向過程,還是拿11(10)這個數字舉例。1011(2)=1000+10+1=23+21+20=8+2+1=11(10)。

3.0.3-0.2不等於0.1和12條問題有什么關系?

  第一條說了因為我們習慣於非二進制,而計算機只能認知二進制,所以我們在編程語言中所寫的那些以十進制表示數值的代碼,對於底層的計算機實現來說是按照二進制來進行的。所以這其中肯定就會有一個進制轉換的問題。

  當我們在高級語言里用十進制書寫代碼,到最后得到一個正確的依然是十進制的結果的這個過程,計算機在中間進行了兩次數制轉換,一次是十進制轉二進制,轉換完后進行數據操作,操作完成后所得到的二進制數據又逆向從二進制轉換為十進制,最后呈現給我們一個我們所預期的結果。

  0.3-0.2不等於0.1的原因就出現在這個過程當中。

4.那么0.3-0.2不等於0.1到底是怎么形成的?

  緊接着上邊的表述往下講,以0.3-0.2為例,在JavaScript語言中完成這一個計算過程。

  首先我們打出0.3-0.2的表達式,本想我們很輕易的得出一個0.1的結果,然而,事實出乎意料,結果卻給我們返回一個0.09999999999999998。

  數據在計算機中的分類大體為符號數和無符號數、定點數和浮點數,相互交叉便可以分為無符號定點數、浮點數和有符號定點數、浮點數。0.3(10)轉換成二進制數據表示為:0.3(10)=2-3+2-4+2-7+2-8+......=0.001100110011001100110011001100110011001100110011001101(2),在JavaScript可以用對象的toSting方法查看,如0.3.toString(2);然后我們拿到這個轉換后的二進制數據再按照單精度float或者雙精度double的格式規格化成符號位階數尾數的浮點數的表示形式,這就完成了從十進制到二進制表示的轉換過程。

  再然后計算機就將0.3和0.2的二進制浮點數形式的表示進行運算,很顯然,這個結果跟我們所想的0.3-0.2就該等於0.1可能會有些差異,因為我們在運算的過程中要對數據進行對階操作,即兩個數的階數不同的話要調整到統一大小的階數,調整過程中一般是右移,右移過程中就會出現精度受損的可能,因此計算完后的結果很有可能就不是我們所想要的那個結果了。

5.0.3-0.2就一定不等於0.1嗎?

  首先,只要是小數,或者說是浮點運算,就有可能會出現精度受損的情況,但是只是可能,也不是說就一定會出現精度受損。具體要看操作數在進制轉換過程中和二進制數值運算過程中有沒有受到精度的影響,你比如0.5-0.25=2-1-2-2=2-2=0.25,這種情況下就不會出現精度受損,所以結果和我們預期的是一樣的。

6.怎么規避這個問題?

  一切問題都可以解決,前提是我們知道了原因。現在我們知道了為什么0.3-0.2不等於0.1,那就反方向考慮解決問題的方法。那就是避免他們在數制轉換過程和二進制數值運算過程中精度受損的影響。比如,我有3毛錢,也就是0.3元,然后買了只雪糕花掉我2毛錢,也就是0.2元,問還剩多少錢?如果是在以前,我們肯定不假思索的0.3-0.2=0.1想當然的就得出了結論。但是現在我們已經認識到這樣會有問題,那么我們怎么避免他呢?那就是我為什么非要用元作單位而不用毛或者用分作單位?如果我用毛作單位3-2=1,用分作單位30-20=10就一定不會出現問題。因為整數的運算肯定不會有浮點運算的精度問題。所以這就是解決問題的思路。

 

完!

祝愉快。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM