我是這樣解決JavaScrip 加減乘除精度問題
前言
眾所周知的 JavaScript 二進制精度問題,浮點數(shù)的計算精度會存在缺失問題。最經(jīng)典的例子就是為什么0.1+0.2 !== 0.3
一句話概括就是:ECMAScript規(guī)范定義Number的類型遵循了IEEE754-2008中的64位浮點數(shù)規(guī)則定義的小數(shù)后的有效位數(shù)至多為52位導(dǎo)致計算出現(xiàn)精度丟失問題!
不過網(wǎng)上已經(jīng)有很多專門的類庫可以解決這個問題。
原生封裝
加
- /**
- ** 加法函數(shù),用來得到精確的加法結(jié)果
- ** 說明:javascript的加法結(jié)果會有誤差,在兩個浮點數(shù)相加的時候會比較明顯。這個函數(shù)返回較為精確的加法結(jié)果。
- ** 調(diào)用:accAdd(arg1,arg2)
- ** 返回值:arg1加上arg2的精確結(jié)果
- **/
- function accAdd(arg1, arg2) {
- let r1, r2, m
- try {
- r1 = arg1.toString().split('.')[1].length
- } catch (e) {
- r1 = 0
- }
- try {
- r2 = arg2.toString().split('.')[1].length
- } catch (e) {
- r2 = 0
- }
- m = Math.pow(10, Math.max(r1, r2))
- return (arg1 * m + arg2 * m) / m
- }
減
- /**
- ** 減法函數(shù),用來得到精確的減法結(jié)果
- ** 說明:javascript的減法結(jié)果會有誤差,在兩個浮點數(shù)相減的時候會比較明顯。這個函數(shù)返回較為精確的減法結(jié)果。
- ** 調(diào)用:accSub(arg1,arg2)
- ** 返回值:arg1加上arg2的精確結(jié)果
- **/
- function accSub(arg1, arg2) {
- var r1, r2, m, n;
- try {
- r1 = arg1.toString().split(".")[1].length;
- } catch (e) {
- r1 = 0;
- }
- try {
- r2 = arg2.toString().split(".")[1].length;
- } catch (e) {
- r2 = 0;
- }
- m = Math.pow(10, Math.max(r1, r2)); //last modify by deeka //動態(tài)控制精度長度
- n = r1 >= r2 ? r1 : r2;
- return ((arg1 * m - arg2 * m) / m).toFixed(n);
- }
乘
- /**
- ** 乘法函數(shù),用來得到精確的乘法結(jié)果
- ** 說明:javascript的乘法結(jié)果會有誤差,在兩個浮點數(shù)相乘的時候會比較明顯。這個函數(shù)返回較為精確的乘法結(jié)果。
- ** 調(diào)用:accMul(arg1,arg2)
- ** 返回值:arg1乘以 arg2的精確結(jié)果
- **/
- function accMul(arg1, arg2) {
- let m = 0
- let s1 = arg1.toString()
- let s2 = arg2.toString()
- try {
- m += s1.split('.')[1] ? s1.split('.')[1].length : ''
- } catch (e) {}
- try {
- m += s2.split('.')[1] ? s2.split('.')[1].length : ''
- } catch (e) {}
- return (Number(s1.replace('.', '')) * Number(s2.replace('.', ''))) / Math.pow(10, m)
- }
除
- /**
- ** 除法函數(shù),用來得到精確的除法結(jié)果
- ** 說明:javascript的除法結(jié)果會有誤差,在兩個浮點數(shù)相除的時候會比較明顯。這個函數(shù)返回較為精確的除法結(jié)果。
- ** 調(diào)用:accDiv(arg1,arg2)
- ** 返回值:arg1除以arg2的精確結(jié)果
- **/
- function accDiv(arg1, arg2) {
- let t1 = 0
- let t2 = 0
- let r1
- let r2
- try {
- t1 = arg1.toString().split('.')[1].length
- } catch (e) {}
- try {
- t2 = arg2.toString().split('.')[1].length
- } catch (e) {}
- r1 = Number(arg1.toString().replace('.', ''))
- r2 = Number(arg2.toString().replace('.', ''))
- return (r1 / r2) * Math.pow(10, t2 - t1)
- }
封裝
定義一個函數(shù)來調(diào)用加減乘除方法,這樣做有個好處,用到地方調(diào)用加減乘除方法一致,假設(shè)某個方法后面發(fā)現(xiàn)那個庫更好用或者某個平臺不兼容、算法不太嚴謹、擴展新的功能等等,我們只要維護這個函數(shù)就行,不用在考慮項目中某個組件單獨引用,沒有按照這個規(guī)范因為這次維護引發(fā)的新問題。
- export const calcFn = {
- add() {
- const arg = Array.from(arguments)
- return arg.reduce((total, num) => {
- return accAdd(total, num)
- })
- },
- sub() {
- const arg = Array.from(arguments)
- return arg.reduce((total, num) => {
- return accSub(total, num)
- })
- },
- mul() {
- const arg = Array.from(arguments)
- return arg.reduce((total, num) => {
- return accMul(total, num)
- })
- },
- divide() {
- const arg = Array.from(arguments)
- return arg.reduce((total, num) => {
- return accDiv(total, num)
- })
- }
- }
big.js
- 介紹:任意精度十進制算術(shù)的小型、快速、易于使用的庫。
- 特性:目前同類型最小包、無依賴、包大小3 KB、兼容ECMAScript 3+可以說適用于所有瀏覽器。
- 官網(wǎng):GitHub
https://github.com/MikeMcl/big.js/
安裝使用
瀏覽器
- <script src='https://cdn.jsdelivr.net/npm/big.js@6.1.1/big.min.js'></script>
Node.js
- npm install big.js
使用
- x = new Big(0.1)
- y = new Big(0.2)
- z = new Big(0.3)
- x.plus(y).eq(z) // true
運算符操作函數(shù)
以下big.js目前支持運算符操作函數(shù)。
- abs,取絕對值。
- cmp,compare的縮寫,即比較函數(shù)。
- div,除法。
- eq,equal的縮寫,即相等比較。
- gt,大于。
- gte,小于等于,e表示equal。
- lt,小于。
- lte,小于等于,e表示equal。
- minus,減法。
- mod,取余。
- plus,加法。
- pow,次方。
- prec,按精度舍入,參數(shù)表示整體位數(shù)。
- round,按精度舍入,參數(shù)表示小數(shù)點后位數(shù)。
- sqrt,開方。
- times,乘法。
- toExponential,轉(zhuǎn)化為科學(xué)計數(shù)法,參數(shù)代表精度位數(shù)。
- toFied,補全位數(shù),參數(shù)代表小數(shù)點后位數(shù)。
- toJSON和toString,轉(zhuǎn)化為字符串。
- toPrecision,按指定有效位數(shù)展示,參數(shù)為有效位數(shù)。
- toNumber,轉(zhuǎn)化為JavaScript中number類型。
- valueOf,包含負號(如果為負數(shù)或者-0)的字符串。
封裝
- import Big from 'big.js'
- export const calcFn = {
- add() {
- const arg = Array.from(arguments)
- return arg.reduce((total, num) => {
- return new Big(total).plus(new Big(num))
- }).toString() * 1
- },
- sub() {
- const arg = Array.from(arguments)
- return arg.reduce((total, num) => {
- return new Big(total).minus(new Big(num))
- }).toString() * 1
- },
- mul() {
- const arg = Array.from(arguments)
- return arg.reduce((total, num) => {
- return new Big(total).times(new Big(num))
- }).toString() * 1
- },
- divide() {
- const arg = Array.from(arguments)
- return arg.reduce((total, num) => {
- return new Big(total).div(new Big(num))
- }).toString() * 1
- }
- }
使用
- calcFn.add(0.1, 0.2) !== 0.3 // false
bignumber.js
- 介紹:用于任意精度十進制和非十進制算術(shù)的 JavaScript 庫。
- 特性:無依賴、包大小8 KB、兼容ECMAScript 3+可以說適用于所有瀏覽器。
- 官網(wǎng):GitHub
https://github.com/MikeMcl/bignumber.js
使用方法類似,同上。
decimal.js
- 介紹:為 JavaScript 提供十進制類型的任意精度數(shù)值。
- 特性:無依賴、包大小12.6 KB、兼容ECMAScript 3+可以說適用于所有瀏覽器。
- 官網(wǎng):GitHub
https://github.com/MikeMcl/decimal.js
使用方法類似,同上。
Math.js
- 介紹:用 Javascript 編寫的簡單數(shù)學(xué)庫,可能不維護了。
- 特性:是一個廣泛的 JavaScript 和 Node.js 數(shù)學(xué)庫。它具有靈活的表達式解析器,支持符號計算,帶有大量內(nèi)置函數(shù)和常量,并提供了一個集成的解決方案來處理不同的數(shù)據(jù)類型,如數(shù)字、大數(shù)、復(fù)數(shù)、分數(shù)、單位和矩陣。功能強大且易于使用。
- 官網(wǎng):GitHub
總結(jié)
big.js適用于大部分十進制算術(shù)應(yīng)用程序,因為不接受NaN或Infinity作為合法值。而且不支持其他基數(shù)的值。如果項目中沒有非十進制算術(shù)這非常適合用,而且關(guān)鍵是包足過小,哈哈自己造的輪子后面還是覺得庫比較香哈。
bignumber.js可能更適合金融應(yīng)用,因為除非使用涉及除法的運算,否則用戶無需擔心會丟失精度。
decimal.js可能更適合更科學(xué)的應(yīng)用程序,因為它可以更有效地處理非常小的或大的值。例如,它沒有bignumber.js的限制,當將一個小指數(shù)的值與一個大指數(shù)的值相加時,bignumber.js會嘗試執(zhí)行全精度運算,這可能會導(dǎo)致操作不可行。
如上所述,decimal.js還支持非整數(shù)冪,并增加了三角函數(shù)和exp,ln和log方法。這些添加使decimal.js明顯大于bignumber.js。