浮点数运算的精度问题:以js语言为例
在 JavaScript 中整數和浮點數都屬于?Number?數據類型,所有數字都是以 64 位浮點數形式儲存,即便整數也是如此。 所以我們在打印?1.00?這樣的浮點數的結果是?1?而非?1.00?。在一些特殊的數值表示中,例如金額,這樣看上去有點變扭,但是至少值是正確了。然而要命的是,當浮點數做數學運算的時候,你經常會發現一些問題,舉幾個例子:
// 加法 ===================== // 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 不會?我們來看看其真正的原因。
JavaScript 里的數字是采用?IEEE 754 標準的 64 位雙精度浮點數。該規范定義了浮點數的格式,對于64位的浮點數在內存中的表示,最高的1位是符號位,接著的11位是指數,剩下的52位為有效數字,具體:
- 第0位:符號位, s 表示 ,0表示正數,1表示負數;
- 第1位到第11位:儲存指數部分, e 表示 ;
- 第12位到第63位:儲存小數部分(即有效數字),f 表示,
如圖:
符號位決定了一個數的正負,指數部分決定了數值的大小,小數部分決定了數值的精度。 IEEE 754規定,有效數字第一位默認總是1,不保存在64位浮點數之中。也就是說,有效數字總是1.xx…xx的形式,其中xx..xx的部分保存在64位浮點數之中,最長可能為52位。因此,JavaScript提供的有效數字最長為53個二進制位(64位浮點的后52位+有效數字第一位的1)。
計算過程
比如在 JavaScript 中計算?0.1 + 0.2時,到底發生了什么呢?
首先,十進制的0.1和0.2都會被轉換成二進制,但由于浮點數用二進制表達時是無窮的,例如。
0.1 -> 0.0001100110011001...(無限) 0.2 -> 0.0011001100110011...(無限)IEEE 754 標準的 64 位雙精度浮點數的小數部分最多支持 53 位二進制位,所以兩者相加之后得到二進制為:
0.0100110011001100110011001100110011001100110011001100因浮點數小數位的限制而截斷的二進制數字,再轉換為十進制,就成了?0.30000000000000004。所以在進行算術計算時會產生誤差。
整數的精度問題
在 Javascript 中,整數精度同樣存在問題,先來看看問題:
console.log(19571992547450991); //=> 19571992547450990 console.log(19571992547450991===19571992547450992); //=> true同樣的原因,在 JavaScript 中?Number類型統一按浮點數處理,整數是按最大54位來算最大(253?- 1,Number.MAX_SAFE_INTEGER,9007199254740991) 和最小(-(253?- 1),Number.MIN_SAFE_INTEGER,-9007199254740991) 安全整數范圍的。所以只要超過這個范圍,就會存在被舍去的精度問題。
當然這個問題并不只是在 Javascript 中才會出現,幾乎所有的編程語言都采用了 IEEE-745 浮點數表示法,任何使用二進制浮點數的編程語言都會有這個問題,只不過在很多其他語言中已經封裝好了方法來避免精度的問題,而 JavaScript 是一門弱類型的語言,從設計思想上就沒有對浮點數有個嚴格的數據類型,所以精度誤差的問題就顯得格外突出。
解決方案
上面說了這么多問題和原因,這里給出一些解決方案。
類庫
通常這種對精度要求高的計算都應該交給后端去計算和存儲,因為后端有成熟的庫來解決這種計算問題。前端也有幾個不錯的類庫:
Math.js
Math.js 是專門為 JavaScript 和 Node.js 提供的一個廣泛的數學庫。它具有靈活的表達式解析器,支持符號計算,配有大量內置函數和常量,并提供集成解決方案來處理不同的數據類型
像數字,大數字(超出安全數的數字),復數,分數,單位和矩陣。 功能強大,易于使用。
官網:http://mathjs.org/
GitHub:https://github.com/josdejong/mathjs
decimal.js
為 JavaScript 提供十進制類型的任意精度數值。
官網:http://mikemcl.github.io/decimal.js/
GitHub:https://github.com/MikeMcl/decimal.js
big.js
官網:http://mikemcl.github.io/big.js
GitHub:https://github.com/MikeMcl/big.js/
這幾個類庫幫我們解決很多這類問題,不過通常我們前端做這類運算通常只用于表現層,應用并不是很多。所以很多時候,一個函數能解決的問題不需要引用一個類庫來解決。
下面介紹各個更加簡單的解決方案。
整數表示
對于整數,我們可以通過用String類型的表示來取值或傳值,否則會喪失精度。
格式化數字、金額、保留幾位小數等
如果只是格式化數字、金額、保留幾位小數等可以查看這里?https://www.html.cn/archives/7324
浮點數運算
toFixed() 方法
浮點數運算的解決方案有很多,這里給出一種目前常用的解決方案, 在判斷浮點數運算結果前對計算結果進行精度縮小,因為在精度縮小的過程總會自動四舍五入。
toFixed() 方法使用定點表示法來格式化一個數,會對結果進行四舍五入。語法為:
numObj.toFixed(digits)參數?digits?表示小數點后數字的個數;介于 0 到 20 (包括)之間,實現環境可能支持更大范圍。如果忽略該參數,則默認為 0。
返回一個數值的字符串表現形式,不使用指數記數法,而是在小數點后有?digits?位數字。該數值在必要時進行四舍五入,另外在必要時會用 0 來填充小數部分,以便小數部分有指定的位數。 如果數值大于?1e+21,該方法會簡單調用?Number.prototype.toString()并返回一個指數記數法格式的字符串。
特別注意:toFixed() 返回一個數值的字符串表現形式。
具體可以查看?MDN中的說明,那么我們可以這樣解決精度問題:
parseFloat((數學表達式).toFixed(digits)); // toFixed() 精度參數須在 0 與20 之間 // 運行 parseFloat((1.0 - 0.9).toFixed(10)) // 結果為 0.1 parseFloat((0.3 / 0.1).toFixed(10)) // 結果為 3 parseFloat((9.7 * 100).toFixed(10)) // 結果為 970 parseFloat((2.22 + 0.1).toFixed(10)) // 結果為 2.32?
在老版本的IE瀏覽器(IE 6,7,8)中,toFixed()方法返回值不一定準確。所以這個方法以前很少用。以至于網上搜索出來的結果大都是下面這些方法。
還有一些其他的解決方案,簡單的說需要將浮點數轉換字符串,分隔成為整數部分和小數部分,小數部分再轉換為整數,計算結果后,再轉換為浮點數。這過程有點復雜…,網上找一下:
加法函數
/**** 加法函數,用來得到精確的加法結果** 說明: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://www.cnblogs.com/tkzc2013/p/10324480.html
總結
以上是生活随笔為你收集整理的浮点数运算的精度问题:以js语言为例的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 构造方法,this,super关键字
- 下一篇: 足球动图gif(一)