JS四則運算與四捨五入精度問題及解決方案

StevenLikeWatermelon發表於2018-11-02

一、Javascript精度問題業務背景

JS中 0.1+0.2 = 0.3000000000000004的問題,在很多業務場景裡都是一個令人頭痛的問題。尤其是在大型的電商企業,貨幣基金股票行業的網頁中,JS四則運算和toFixed精度問題更是讓人防不勝防。 京東曾經發生過一起線上toFixed精度問題,差點釀成大錯:

京東
雖然看起來只有0.01的差別,但是如果消費者用來投訴甚至打官司,這完全可以成為欺詐消費者的理由。原因就是JS裡“該死”的toFixed方法。

二、剖析Javascript精度問題原因

1、超大數和浮點數四則運算精度問題原因

在剖析JS精度問題之前,要簡單描述下JS計算的方式背景

1、在JS內部所有的計算都是以二進位制方式計算的。

2、JS內部無法無限制儲存二進位制數值的長度。(最長52位)

這兩點其實都不難理解。計算機底層都是0和1,當然,計算機也不能保留無限長(無限大)的東西。

知道了這兩點,那麼自然也就不難理解為什麼JS在計算超大的數值的時候,會出現問題了,就好像你永遠無法算清宇宙裡有多少顆星星一樣,計算機也不行。

那麼為什麼計算很小浮點數的時候也會出問題呢,答案就是,在JS內部,浮點數也是用很長很長的二進位制表示的(具體為什麼請參考計算機原理)。

在JS的世界裡,0.1轉換為二進位制是0.00011001100110011…你會發現這串數字是無數個0.11在迴圈..(0.0(0011)(0011)(0011)(0011)…還有無數0011)沒辦法,在JS裡,浮點數就是轉化為無窮長的二進位制,所以JS只能擷取這串二進位制的前52位進行二進位制的相加,那麼下面不用再說了,自然就會出現精度問題。

這也可以解釋,為什麼JS中整型的數值加減不會出現問題,但是超大數值和浮點數計算會出現問題。

2、toFixed()精度問題

toFixed問題其實很簡單,就是瀏覽器的坑。

圖片描述

圖片描述

可以看到,在IE和chrome兩個瀏覽器下面,一個簡單的四捨五入,顯示的結果完全不同。就是因為不同的瀏覽器廠商對於四捨五入沒有統一的標準,就讓我們這些開發人員踩坑。。 在IE裡,toFixed就是採用常規的四捨五入方法,然鵝,在chrome卻是採用金融界的更為精確的四捨六入五成雙方法,雖然這個方法更為科學,更為精確,但是卻畢竟大多數人不是金融學家也不是科學家,還是四捨五入更容易被常人所接受。。。

更正:

在toFixed精度問題上直接參考了大部分網路解釋,欠缺實踐,沒有深入考究,上面四捨六入五成雙的方法經反覆驗證其實是錯誤的(上圖就打臉了。。),再次查閱一些資料後,經過官方查證toFixed 原生API如下:

toFixed Api
大概意思就是如果toFixed的入參小於10的21次方,那麼就取一個整數n,讓n*10^f - x 的精確值儘可能的趨近於0,如果存在兩個這樣的n,取較大的n

toFixed
上圖例子中1.335.toFixed(2)按照四捨六入五成雙應該是1.34,但是實際情況確實1.33。而用官方的API就可以解釋為什麼是1.33,因為n=133的時候讓n*10^f - x更趨近於0(雖然結果又出現了四則運算的精度問題,但是確實是符合規則的),所以最後得到的結果是1.33。

這個方法經過10幾次toFixed驗證後發現都是和結果相吻合的,應該是正確的問題根源所在。

(PS:該錯誤對解決方案無影響)

三、精度問題終極解決方案。

1、四則運算精度問題解決方案

前面提到JS中整型運算是沒有問題的,那麼把浮點數擴大相應的整數倍,轉化成整型在進行計算,計算完縮小為整數倍不就可以解決這個問題了麼。 這個方案確實是可行的,然鵝,怎麼實施這個方案,卻需要琢磨下。 首先第一想到的是乘法,512.06*100就可以轉化為 51206了,然鵝。。

圖片描述
有點坑,因為浮點數的計算本身就會有問題,顯然想簡單的通過乘法來實現整型轉化,是不靠譜的,這時候就要考慮自己實現一個萬無一失的整型轉化方法。 將數字當做字串,手動的進行小數點移位,這個方案把浮點型轉換為整型是完全可行的,接下來就考慮怎麼實現的問題。 因為在JS裡,超過一定大小的number就會被JS自動轉化為科學計演算法,所以在考慮把number當成字串處理時。一定要考慮到這一點。

解決方案的思路就是這樣,具體的程式碼寫起來很相對比較複雜,我參考網上的function自己整合了一個npm包,需要的朋友可以自己檢視下原始碼。 總體的思路就是上文介紹的,具體程式碼實現可以參考原始碼(如果能順便點個贊,那最好啦)

2、toFixed解決方案

toFixed的方案相對要好解決,只要有了精度沒問題的四則運算,四捨五入只要直接判斷下浮點數需要精確位數是否大於5即可,具體的程式碼實現也可以參考原始碼

四、總結

解決方案核心的點就在於用字串方式來解決小數點精度問題,雖然實現上有點複雜,需要考慮的情況比較多,但是確實是最穩妥的做法。如果有需要高精度業務場景的小夥伴可以直接install我封裝的npm包,就可以直接用啦,教程參考github哦!

最後,如果這篇文章對你有點點幫助的話,歡迎動動滑鼠點個贊哦!

(純屬個人手動打字,有手誤或者技術錯誤的地方,肯定多多指正!)

相關文章