有時需求中會有前端校驗輸入數字金額的時候,判斷,幾個輸入框的金額合計是否大於小於或等於某個整數,在輸入的值可以為小數的時候,很容易就出現js小數點計算丟失精度問題。比如下圖
js高級程序設計(我這版是第3版)在3.4.5Number類型這節中就談到了這個現象,原話是:
關於浮點數值計算會產生攝入誤差的問題,有一點需要明確:這是使用基於IEEE754數值的浮點計算的通病,ESMAScript並非獨此一家,其他使用相同數值格式的語言也存在這個問題。
所以即使浮點數值的最高精度是17位小數,但在進行算術計算時其精度遠遠不如整數。
1.使用toFixed(x)方法,x為必需,規定小數的位數,是 0 ~ 20 之間的值,包括 0 和 20,如果省略了該參數,將用 0 代替。
比如:
但是這種方法的局限性是不能使用toFixed(x)去進行舍入操作。因為IEEE754標准規定的浮點數取整算法是銀行家舍入法,即四舍六入五留雙法:四舍六入五考慮,五后非零就進一,五后為零看奇偶,五前為偶應舍去,五前為奇要進一。
極其不推薦對小數有舍入操作的時候使用toFixed(x)。
小數點計算的時候精度只能用於所有操作數中最多小數位的精度計算,即toFixed(x)中的x必須大於等於最多小數位操作數的小數位數量,否則就會丟失精度。
2.使用第三方庫
比如Math.js,decimal.js,使用方法大同小異,詳情見:
https://www.npmjs.com/package/mathjs
https://www.npmjs.com/package/decimal.js/v/3.0.0
以vue2.x為例:
先npm install mathjs / decimal.js
然后在main.js中引入,以ES模塊規范為例:
math.js是這樣使用的:(詳見math.js文檔:https://mathjs.org/docs/index.html)
import * as math from "mathjs"; const config = { epsilon: 1e-12, matrix: 'Matrix', number: 'number', //運算時需要精度准確時此處需配置為BigNumber precision: 64, //僅在number類型為BigNumbers生效 predictable: false, randomSeed: null //選項設置為種子偽隨機數生成,使其成為確定性的。 } const math = create(all, config); let number = math.add(math.bignumber(0.1), math.bignumber(0.2)); //操作數中至少要有一個調用bignumber()
decimal.js是這樣使用的:(詳見decimal.js文檔:http://mikemcl.github.io/decimal.js/)
import { Decimal } from 'decimal.js'; let number = new Decimal(0.11).add(new Decimal(0.29));
其他
本來到前面就到尾聲了,但是這里還是需要啰嗦一下,我還看過一些博客還提到一個方法或是自己封裝的方法,其核心是先將小數*10的n次方放大為整數,只要最終結果在Number類型邊界之內的整數運算是不會有精度誤差的,然后再將整數的運算結果除於10的n次方就得到最終的結果。但是,這個方法沒寫好也是存在問題的,就比如前面舉的例子:0.07*100,結果就不是7。因為只有少數幾個小數可以被浮點數精確表示,比如0.25的N次方,或是0.75的n次方,其他的小數基本都是近似數。其實我也很納悶0.06,0.08也都是近似數,但是他們相乘100結果分別就是6,8。有些博主通過這個方法封裝的方法都沒考慮到這塊,所以在相乘放大過程中就已經出錯了,但是如果在放大縮小過程中能控制住精度,也仍不失為一個解決問題的好方法。