在 JavaScript 浮點數做數學運算的時候,你經常會發現一些問題,舉幾個例子:
// 加法 ===================== // 0.1 + 0.2 = 0.30000000000000004 // 0.7 + 0.1 = 0.7999999999999999 // 0.2 + 0.4 = 0.6000000000000001 // 2.22 + 0.1 = 2.3200000000000003 // 減法 ===================== // 1.5 - 1.2 = 0.30000000000000004 // 0.3 - 0.2 = 0.09999999999999998 // 乘法 ===================== // 19.9 * 100 = 1989.9999999999998 // 19.9 * 10 * 10 = 1990 // 1306377.64 * 100 = 130637763.99999999 // 1306377.64 * 10 * 10 = 130637763.99999999 // 0.7 * 180 = 125.99999999999999 // 9.7 * 100 = 969.9999999999999 // 39.7 * 100 = 3970.0000000000005 // 除法 ===================== // 0.3 / 0.1 = 2.9999999999999996 // 0.69 / 10 = 0.06899999999999999
在 JavaScript 中計算 0.1 + 0.2
時,到底發生了什么呢?
首先,十進制的0.1
和0.2
都會被轉換成二進制,但由於浮點數用二進制表達時是無窮的,例如。
JavaScript 代碼:
0.1 -> 0.0001100110011001...(無限) 0.2 -> 0.0011001100110011...(無限)
IEEE 754 標准的 64 位雙精度浮點數的小數部分最多支持 53 位二進制位,所以兩者相加之后得到二進制為:
JavaScript 代碼:
0.0100110011001100110011001100110011001100110011001100
因浮點數小數位的限制而截斷的二進制數字,再轉換為十進制,就成了 0.30000000000000004
。所以在進行算術計算時會產生誤差。
一些解決方案。
通常這種對精度要求高的計算都應該交給后端去計算和存儲,因為后端有成熟的庫來解決這種計算問題。前端也有幾個不錯的類庫:
Math.js
Math.js 是專門為 JavaScript 和 Node.js 提供的一個廣泛的數學庫。它具有靈活的表達式解析器,支持符號計算,配有大量內置函數和常量,並提供集成解決方案來處理不同的數據類型
像數字,大數字(超出安全數的數字),復數,分數,單位和矩陣。 功能強大,易於使用。
還有幾個類庫自行百度。
重點講解自定義方法,需要將浮點數轉換字符串,分隔成為整數部分和小數部分,小數部分再轉換為整數,計算結果后,再轉換為浮點數。如下:
/** ** 加法函數,用來得到精確的加法結果 ** 說明:javascript的加法結果會有誤差,在兩個浮點數相加的時候會比較明顯。這個函數返回較為精確的加法結果。 ** 調用:accAdd(arg1,arg2) ** 返回值:arg1加上arg2的精確結果 **/ function accAdd(arg1, arg2) { var r1, r2, m, c; try { r1 = arg1.toString().split(".")[1].length; } catch (e) { r1 = 0; } try { r2 = arg2.toString().split(".")[1].length; } catch (e) { r2 = 0; } c = Math.abs(r1 - r2); m = Math.pow(10, Math.max(r1, r2)); if (c > 0) { var cm = Math.pow(10, c); if (r1 > r2) { arg1 = Number(arg1.toString().replace(".", "")); arg2 = Number(arg2.toString().replace(".", "")) * cm; } else { arg1 = Number(arg1.toString().replace(".", "")) * cm; arg2 = Number(arg2.toString().replace(".", "")); } } else { arg1 = Number(arg1.toString().replace(".", "")); arg2 = Number(arg2.toString().replace(".", "")); } return (arg1 + arg2) / m; } //給Number類型增加一個add方法,調用起來更加方便。 Number.prototype.add = function(arg) { return accAdd(arg, this); }; /** ** 減法函數,用來得到精確的減法結果 ** 說明:javascript的減法結果會有誤差,在兩個浮點數相減的時候會比較明顯。這個函數返回較為精確的減法結果。 ** 調用:accSub(arg1,arg2) ** 返回值:arg1加上arg2的精確結果 **/ 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 //動態控制精度長度 n = (r1 >= r2) ? r1 : r2; return ((arg1 * m - arg2 * m) / m).toFixed(n); } // 給Number類型增加一個mul方法,調用起來更加方便。 Number.prototype.sub = function(arg) { return accMul(arg, this); }; /** ** 乘法函數,用來得到精確的乘法結果 ** 說明:javascript的乘法結果會有誤差,在兩個浮點數相乘的時候會比較明顯。這個函數返回較為精確的乘法結果。 ** 調用:accMul(arg1,arg2) ** 返回值:arg1乘以 arg2的精確結果 **/ function accMul(arg1, arg2) { var m = 0, s1 = arg1.toString(), s2 = arg2.toString(); try { m += s1.split(".")[1].length; } catch (e) {} try { m += s2.split(".")[1].length; } catch (e) {} return Number(s1.replace(".", "")) * Number(s2.replace(".", "")) / Math.pow(10, m); } // 給Number類型增加一個mul方法,調用起來更加方便。 Number.prototype.mul = function(arg) { return accMul(arg, this); }; /** ** 除法函數,用來得到精確的除法結果 ** 說明:javascript的除法結果會有誤差,在兩個浮點數相除的時候會比較明顯。這個函數返回較為精確的除法結果。 ** 調用:accDiv(arg1,arg2) ** 返回值:arg1除以arg2的精確結果 **/ function accDiv(arg1, arg2) { var t1 = 0, t2 = 0, r1, r2; try { t1 = arg1.toString().split(".")[1].length; } catch (e) {} try { t2 = arg2.toString().split(".")[1].length; } catch (e) {} with(Math) { r1 = Number(arg1.toString().replace(".", "")); r2 = Number(arg2.toString().replace(".", "")); return (r1 / r2) * pow(10, t2 - t1); } } //給Number類型增加一個div方法,調用起來更加方便。 Number.prototype.div = function(arg) { return accDiv(this, arg); };
原文:https://blog.csdn.net/payne_liu/article/details/100513289