JavaScript計算出現(xiàn)精度丟失問題的解決方法
前言
Javascript作為一門大型編程語言,在日常開發(fā)中難免會涉及到大量的數(shù)學(xué)計算。然而,浮點數(shù)在計算過程中可能出現(xiàn)精度的問題,因此Javascript提供了一個高精度計算庫來幫助處理復(fù)雜的數(shù)字計算。本文就來介紹一下Javascript高精度計算及其相關(guān)知識。
首先,我們來看一個簡單的例子:
0.1 + 0.2
結(jié)果不是 0.3,而是 0.30000000000000004
可以看到數(shù)字的精度已經(jīng)丟失,雖然結(jié)果相差無幾,但是作為技術(shù)人員,這絕對不可以忽略。 簡單一句話概括解釋為什么你會得到意想不到的結(jié)果:
因為在計算機內(nèi)部,使用的二進制浮點根本就不能準確地表示像 0.1, 0.2 或 0.3 這樣的數(shù)字。
當(dāng)編碼或解釋代碼時,你的 “0.1” 其實已經(jīng)舍入為和該數(shù)字的最接近的數(shù)字,即使在計算發(fā)生之前已經(jīng)會導(dǎo)致小的舍入誤差。
JavaScript 中的數(shù)字都是浮點數(shù),即使看起來像整數(shù)的數(shù)字也是。這是因為 JavaScript 使用 IEEE 754 標準來表示數(shù)字,這種表示方法對于大多數(shù)情況是足夠的,但在某些情況下可能導(dǎo)致精度丟失。
在涉及貨幣或其他需要精確計算的場景中,由于 JavaScript 浮點數(shù)的特性可能導(dǎo)致精度丟失,因此一種常見而有效的解決方案是將數(shù)字轉(zhuǎn)換為整數(shù)進行計算,然后再將結(jié)果轉(zhuǎn)換回浮點數(shù)。這種做法能夠在一定程度上規(guī)避浮點數(shù)運算中可能出現(xiàn)的舍入誤差,尤其在處理金融數(shù)據(jù)等對精確性要求極高的情況下顯得尤為重要。
let num1 = 0.1 * 10; // 轉(zhuǎn)換成整數(shù)進行計算 let num2 = 0.2 * 10; let sum = (num1 + num2) / 10; // 轉(zhuǎn)換回浮點數(shù) console.log(sum); // 輸出:0.3
通過上面這種方式,我們可以在保留所需精度的同時,規(guī)避掉 JavaScript 浮點數(shù)運算可能引發(fā)的不精確性問題。
但是也會出現(xiàn)其他問題,增加小數(shù)點后面的位數(shù),會出現(xiàn)下面的情況:
20.24*100
// 2023.9999999999998
我們知道浮點型數(shù)據(jù)類型主要有:單精度float、雙精度double。
但是?。?!
JavaScript 存儲小數(shù)和其它語言如 Java 和 Python 都不同,JavaScript 中所有數(shù)字包括整數(shù)和小數(shù)都只有一種類型 即 Number類型 它的實現(xiàn)遵循 IEEE 754 標準,IEEE 754 標準的內(nèi)容都有什么,這個咱不用管,我們只需要記住以下一點:
javascript以64位雙精度浮點數(shù)存儲所有Number類型值,即計算機最多存儲64位二進制數(shù)。
對于double型數(shù)據(jù)(雙精度浮點數(shù)),其長度是8個字節(jié)(大小),右邊52位用來表示小數(shù)點后面的數(shù)字,中間11位表示e(exponent)小數(shù)點移動的位數(shù),左邊一位用來表示正負。如圖所示:
解決方法
Number(parseFloat(20.24*100).toPrecision(16))
存儲二進制時小數(shù)點的偏移量最大為52位,最多可表示的十進制為9007199254740992,對應(yīng)科學(xué)計數(shù)尾數(shù)是 9.007199254740992,這也是 JavaScript 最多能表示的精度。它的長度是 16,所以可以使用 toPrecision(16) 來做精度運算。
通過先轉(zhuǎn)為浮點型計算,然后做精度運算后再轉(zhuǎn)為Number類型即可。
但是不能保證還會不會有其他問題,并且這樣的計算太繁瑣,每次都需要對數(shù)字進行相應(yīng)的處理。
解決方案
我們將處理的計算問題進行統(tǒng)一封裝,可以專門處理精度問題。代碼如下:
export class Calc{ /** * 加法運算 * @param {number} num1 * @param {number} num2 * @returns {*} */ add(num1: number, num2: number): number { num1 = Number(num1); num2 = Number(num2); let dec1: number, dec2: number, times: number; try { dec1 = this.countDecimals(num1)+1; } catch (e) { dec1 = 0; } try { dec2 = this.countDecimals(num2)+1; } catch (e) { dec2 = 0; } times = Math.pow(10, Math.max(dec1, dec2)); const result = (this.mul(num1, times) + this.mul(num2, times)) / times; return this.getCorrectResult("add", num1, num2, result); } /** * 減法運算 * @param {number} num1 * @param {number} num2 * @returns {number} */ sub(num1: number, num2: number): number { num1 = Number(num1); num2 = Number(num2); let dec1: number, dec2: number, times: number; try { dec1 = this.countDecimals(num1)+1; } catch (e) { dec1 = 0; } try { dec2 = this.countDecimals(num2)+1; } catch (e) { dec2 = 0; } times = Math.pow(10, Math.max(dec1, dec2)); const result = Number((this.mul(num1, times) - this.mul(num2, times)) / times); return this.getCorrectResult("sub", num1, num2, result); } /** * 除法運算 * @param {number} num1 * @param {number} num2 * @returns {number} */ div(num1: number, num2: number): number { num1 = Number(num1); num2 = Number(num2); let t1 = 0, t2 = 0, dec1: number, dec2: number; try { t1 = this.countDecimals(num1); } catch (e) { } try { t2 = this.countDecimals(num2); } catch (e) { } dec1 = this.convertToInt(num1); dec2 = this.convertToInt(num2); const result = this.mul((dec1 / dec2), Math.pow(10, t2 - t1)); return this.getCorrectResult("div", num1, num2, result); } /** * 乘法運算 * @param {number} num1 * @param {number} num2 * @returns {number} */ mul(num1: number, num2: number): number { num1 = Number(num1); num2 = Number(num2); let times = 0, s1 = num1.toString(), s2 = num2.toString(); try { times += this.countDecimals(s1); } catch (e) { } try { times += this.countDecimals(s2); } catch (e) { } const result = this.convertToInt(s1) * this.convertToInt(s2) / Math.pow(10, times); return this.getCorrectResult("mul", num1, num2, result); } /** * 計算小數(shù)位的長度 * @param {*} num * @returns {number} */ private countDecimals(num: any): number { let len = 0; try { num = Number(num); let str = num.toString().toUpperCase(); if (str.split('E').length === 2) { // 科學(xué)記數(shù)法 let isDecimal = false; if (str.split('.').length === 2) { str = str.split('.')[1]; if (parseInt(str.split('E')[0]) !== 0) { isDecimal = true; } } let x = str.split('E'); if (isDecimal) { len = x[0].length; } len -= parseInt(x[1]); } else if (str.split('.').length === 2) { // 十進制 if (parseInt(str.split('.')[1]) !== 0) { len = str.split('.')[1].length; } } } catch(e) { throw e; } finally { if (isNaN(len) || len < 0) { len = 0; } return len; } } /** * 將小數(shù)轉(zhuǎn)成整數(shù) * @param {*} num * @returns {*} */ private convertToInt (num: any): number { num = Number(num); let newNum = num; let times = this.countDecimals(num); let temp_num = num.toString().toUpperCase(); if (temp_num.split('E').length === 2) { newNum = Math.round(num * Math.pow(10, times)); } else { newNum = Number(temp_num.replace(".", "")); } return newNum; } /** * 確認我們的計算結(jié)果無誤,以防萬一 * @param {string} type * @param {number} num1 * @param {number} num2 * @param {number} result * @returns {number} */ private getCorrectResult(type: 'add' | 'sub' | 'div' | 'mul', num1: number, num2: number, result: number): number { let temp_result = 0; switch (type) { case "add": temp_result = num1 + num2; break; case "sub": temp_result = num1 - num2; break; case "div": temp_result = num1 / num2; break; case "mul": temp_result = num1 * num2; break; } if (Math.abs(result - temp_result) > 1) { return temp_result; } return result; } }
希望這個方法能夠幫助到遇到問題的小伙伴們。
總結(jié)
JavaScript 中的浮點數(shù)丟失精度問題是由底層表示方式引起的,因此在進行重要的精確計算時需要格外小心。選擇合適的方法,如整數(shù)計算、使用專門的庫或小數(shù)點后截斷,可以幫助我們在實際應(yīng)用中處理這些問題,確保得到精確的結(jié)果。在不同場景中選擇適當(dāng)?shù)姆椒?,是程序員需要謹慎考慮的問題,以避免潛在的錯誤。
到此這篇關(guān)于JavaScript計算出現(xiàn)精度丟失問題的解決方法的文章就介紹到這了,更多相關(guān)JavaScript精度丟失內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JavaScript 關(guān)于元素獲取焦點(隱藏元素與div)
關(guān)于元素獲取焦點要注意2個小問題,需要的朋友可以參考下。2011-01-01javascript 從if else 到 switch case 再到抽象
大家覺得在接手遺留代碼時,見到什么東東是最讓人感到不耐煩的?復(fù)雜無比的 UML ?我覺得不是。2010-07-07第九篇Bootstrap導(dǎo)航菜單創(chuàng)建步驟詳解
這篇文章主要介紹了Bootstrap導(dǎo)航菜單創(chuàng)建步驟詳解的相關(guān)資料,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2016-06-06JavaScript實現(xiàn)列出數(shù)組中最長的連續(xù)數(shù)
這篇文章主要介紹了JavaScript實現(xiàn)列出數(shù)組中最長的連續(xù)數(shù)的方法及使用,需要的朋友可以參考下2014-12-12學(xué)習(xí)JavaScript設(shè)計模式(繼承)
這篇文章主要帶領(lǐng)大家學(xué)習(xí)JavaScript設(shè)計模式,其中重點介紹繼承,舉例說明為什么需要繼承,對繼承進行詳細剖析,感興趣的小伙伴們可以參考一下2015-11-11bootstrap select2插件用ajax來獲取和顯示數(shù)據(jù)的實例
今天小編就為大家分享一篇bootstrap select2插件用ajax來獲取和顯示數(shù)據(jù)的實例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-08-08