0.1 + 0.2 = ?javascript
在瀏覽器中測試下計算結果,獲得的結果是 0.30000000000000004,並非理想中的 0.3 結果值。爲何會存在這樣的偏差呢?css
數值運算會存在精度丟失的問題html
想要弄清這個問題,得先了解計算機是何如存儲數值的。java
javascript 中的 Number 類型值能夠是十進制,八進制以及十六進制的數值,在進行算數運算時,全部的八進制和十六進制的數值最終都將會被轉換成十進制數值。而對於編程語言來講,全部的程序都會通過解釋,編譯等操做轉換成 CPU 所能識別的語言才能運行,對於 CPU 來講只能識別二進制的數值,因此全部的數值都將會被轉換成二進制數值存儲的計算機內存中。因此,javascript 在算數運算過程當中的順序應該是這樣的,若是存在八進制或者十六進制數值 -> 轉換成十進制數值 -> 轉換成二進制數值 -> 表示成 IEEE754形式的值存儲的內存 中 -> 運算 -> 將結果轉成十進制數值。git
IEEE754 標準規定了32位單精度浮點數在計算機存儲中表示用1位表示數字的符號,用8位表示指數,用23位來表示尾數,而64位雙精度浮點數則是用1位表示數字的符號,用11位表示指數,用52位表示尾數。github
在 javascript 中,Number 類型的數值都是雙精度64位浮點數,那麼就符合 IEEE754 標準的雙精度浮點數規則,結構以下:web
從結構圖中能夠看出,存儲的尾數的長度是52位有效數字,二進制的第一位有效數字一定是1,因此這個值不會被存儲在64位中,節省了一個存儲空間,因此尾數的最長長度應該是53位有效數字。編程
回到剛纔的問題,按照正常的流程是會先將 0.1 和 0.2 轉換成二進制數值。segmentfault
十進制的 0.1 和 0.2 轉換成二進制數值都會是無限循環的值瀏覽器
0.1 -> 0.0001100110011001...(無限) 0.2 -> 0.0011001100110011...(無限)
而根據 IEEE754 標準,尾數最多能存儲53位有效數字,那麼就必須在特定的位置進行四捨五入處理,獲得的結果分別是:
0.1 -> 0.0001100110011001100110011001100110011001100110011001101 0.2 -> 0.001100110011001100110011001100110011001100110011001101
因此,相加獲得的二進制結果爲:
0.0001100110011001100110011001100110011001100110011001101 + 0.001100110011001100110011001100110011001100110011001101 = 0.0100110011001100110011001100110011001100110011001100111
二進制結果轉換成十進制就是 0.30000000000000004。
數值運算會存在精度丟失的問題的緣由是,十進制的數值會先轉成二進制數值存儲在內存中,可是大多數十進制浮點數轉換成二進制是一個無限循環的值,而計算機中存儲的二進制值的尾數最多隻能53位,那麼就會進行四捨五入處理,這樣處理的結果就會致使精度丟失。
既然知道在運算過程當中會存在精度丟失的狀況致使計算不許確,那麼應該如何處理這種問題?
能夠在網上搜索一些成熟完善的插件,例如 mathjs。固然在簡單的場景下,也能夠本身來處理這類問題。
研究過 iview 和 element UI 的 InputNumber 計數器組件源碼,大體的解決思路就是,兩個數進行相加或者相減運算時,若是兩個數中至少一個是小數值,那麼就先將這兩個數擴大10的n次方倍數進行運算,再將運算結果去除以擴大的倍數,獲得最終的結果值,擴大的倍數由小數值的小數位數決定。若是隻存在一個小數值,那麼n的值就是小數的小數位長度,若是兩個值都是小數,那麼就先比較下哪一個值的小數位長度較長,n的值就取較長的小數位長度。
用 Vue 來實現下這種解決方案。
<div id="app" class="demo"> <div class="add-main"> <input type="number" v-model="add_num1"></input> + <input type="number" v-model="add_num2"></input> <i-button type="primary" @click="getReault">計算結果</i-button> = {{ result }} </div> </div>
new Vue({ el: '#app', data: { add_num1: 0, add_num2: 0, result: 0 }, methods: { toPrecision: function(data, maxPrecision) { if (maxPrecision === undefined) maxPrecision = 0; return parseFloat(parseFloat(Number(data).toFixed(maxPrecision))); }, getDecimalLen: function(val) { return val.toString().split(".")[1] ? val.toString().split(".")[1].length : 0; }, getReault: function() { var vm = this; var expandPrecision = null; var decimal_num1 = vm.getDecimalLen(parseFloat(vm.add_num1)); var decimal_num2 = vm.getDecimalLen(parseFloat(vm.add_num2)); let maxPrecision = Math.max(decimal_num1, decimal_num2); expandPrecision = Math.pow(10, maxPrecision); //簡單寫法 // vm.result = (vm.add_num1 * expandPrecision + vm.add_num2 * expandPrecision) / expandPrecision; //嚴謹寫法 vm.result = vm.toPrecision((vm.add_num1 * expandPrecision + vm.add_num2 * expandPrecision) / expandPrecision, maxPrecision); } } })
這種方案只適用於簡單的運用場景,在涉及金額等複雜的運算場景中,最好是選擇第三方完善的插件。