JavaScript計(jì)算出現(xiàn)精度丟失問題的解決方法
前言
Javascript作為一門大型編程語言,在日常開發(fā)中難免會(huì)涉及到大量的數(shù)學(xué)計(jì)算。然而,浮點(diǎn)數(shù)在計(jì)算過程中可能出現(xiàn)精度的問題,因此Javascript提供了一個(gè)高精度計(jì)算庫(kù)來幫助處理復(fù)雜的數(shù)字計(jì)算。本文就來介紹一下Javascript高精度計(jì)算及其相關(guān)知識(shí)。
首先,我們來看一個(gè)簡(jiǎn)單的例子:
0.1 + 0.2
結(jié)果不是 0.3,而是 0.30000000000000004
可以看到數(shù)字的精度已經(jīng)丟失,雖然結(jié)果相差無幾,但是作為技術(shù)人員,這絕對(duì)不可以忽略。 簡(jiǎn)單一句話概括解釋為什么你會(huì)得到意想不到的結(jié)果:
因?yàn)樵谟?jì)算機(jī)內(nèi)部,使用的二進(jìn)制浮點(diǎn)根本就不能準(zhǔn)確地表示像 0.1, 0.2 或 0.3 這樣的數(shù)字。
當(dāng)編碼或解釋代碼時(shí),你的 “0.1” 其實(shí)已經(jīng)舍入為和該數(shù)字的最接近的數(shù)字,即使在計(jì)算發(fā)生之前已經(jīng)會(huì)導(dǎo)致小的舍入誤差。
JavaScript 中的數(shù)字都是浮點(diǎn)數(shù),即使看起來像整數(shù)的數(shù)字也是。這是因?yàn)?JavaScript 使用 IEEE 754 標(biāo)準(zhǔn)來表示數(shù)字,這種表示方法對(duì)于大多數(shù)情況是足夠的,但在某些情況下可能導(dǎo)致精度丟失。
在涉及貨幣或其他需要精確計(jì)算的場(chǎng)景中,由于 JavaScript 浮點(diǎn)數(shù)的特性可能導(dǎo)致精度丟失,因此一種常見而有效的解決方案是將數(shù)字轉(zhuǎn)換為整數(shù)進(jìn)行計(jì)算,然后再將結(jié)果轉(zhuǎn)換回浮點(diǎn)數(shù)。這種做法能夠在一定程度上規(guī)避浮點(diǎn)數(shù)運(yùn)算中可能出現(xiàn)的舍入誤差,尤其在處理金融數(shù)據(jù)等對(duì)精確性要求極高的情況下顯得尤為重要。
let num1 = 0.1 * 10; // 轉(zhuǎn)換成整數(shù)進(jìn)行計(jì)算 let num2 = 0.2 * 10; let sum = (num1 + num2) / 10; // 轉(zhuǎn)換回浮點(diǎn)數(shù) console.log(sum); // 輸出:0.3
通過上面這種方式,我們可以在保留所需精度的同時(shí),規(guī)避掉 JavaScript 浮點(diǎn)數(shù)運(yùn)算可能引發(fā)的不精確性問題。
但是也會(huì)出現(xiàn)其他問題,增加小數(shù)點(diǎn)后面的位數(shù),會(huì)出現(xiàn)下面的情況:
20.24*100
// 2023.9999999999998
我們知道浮點(diǎn)型數(shù)據(jù)類型主要有:?jiǎn)尉萬loat、雙精度double。
但是?。?!
JavaScript 存儲(chǔ)小數(shù)和其它語言如 Java 和 Python 都不同,JavaScript 中所有數(shù)字包括整數(shù)和小數(shù)都只有一種類型 即 Number類型 它的實(shí)現(xiàn)遵循 IEEE 754 標(biāo)準(zhǔn),IEEE 754 標(biāo)準(zhǔn)的內(nèi)容都有什么,這個(gè)咱不用管,我們只需要記住以下一點(diǎn):
javascript以64位雙精度浮點(diǎn)數(shù)存儲(chǔ)所有Number類型值,即計(jì)算機(jī)最多存儲(chǔ)64位二進(jìn)制數(shù)。
對(duì)于double型數(shù)據(jù)(雙精度浮點(diǎn)數(shù)),其長(zhǎng)度是8個(gè)字節(jié)(大小),右邊52位用來表示小數(shù)點(diǎn)后面的數(shù)字,中間11位表示e(exponent)小數(shù)點(diǎn)移動(dòng)的位數(shù),左邊一位用來表示正負(fù)。如圖所示:

解決方法
Number(parseFloat(20.24*100).toPrecision(16))
存儲(chǔ)二進(jìn)制時(shí)小數(shù)點(diǎn)的偏移量最大為52位,最多可表示的十進(jìn)制為9007199254740992,對(duì)應(yīng)科學(xué)計(jì)數(shù)尾數(shù)是 9.007199254740992,這也是 JavaScript 最多能表示的精度。它的長(zhǎng)度是 16,所以可以使用 toPrecision(16) 來做精度運(yùn)算。
通過先轉(zhuǎn)為浮點(diǎn)型計(jì)算,然后做精度運(yùn)算后再轉(zhuǎn)為Number類型即可。
但是不能保證還會(huì)不會(huì)有其他問題,并且這樣的計(jì)算太繁瑣,每次都需要對(duì)數(shù)字進(jìn)行相應(yīng)的處理。
解決方案
我們將處理的計(jì)算問題進(jìn)行統(tǒng)一封裝,可以專門處理精度問題。代碼如下:
export class Calc{
/**
* 加法運(yùn)算
* @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);
}
/**
* 減法運(yùn)算
* @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);
}
/**
* 除法運(yùn)算
* @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);
}
/**
* 乘法運(yùn)算
* @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);
}
/**
* 計(jì)算小數(shù)位的長(zhǎng)度
* @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) { // 十進(jìn)制
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;
}
/**
* 確認(rèn)我們的計(jì)算結(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;
}
}
希望這個(gè)方法能夠幫助到遇到問題的小伙伴們。
總結(jié)
JavaScript 中的浮點(diǎn)數(shù)丟失精度問題是由底層表示方式引起的,因此在進(jìn)行重要的精確計(jì)算時(shí)需要格外小心。選擇合適的方法,如整數(shù)計(jì)算、使用專門的庫(kù)或小數(shù)點(diǎn)后截?cái)?,可以幫助我們?cè)趯?shí)際應(yīng)用中處理這些問題,確保得到精確的結(jié)果。在不同場(chǎng)景中選擇適當(dāng)?shù)姆椒?,是程序員需要謹(jǐn)慎考慮的問題,以避免潛在的錯(cuò)誤。
到此這篇關(guān)于JavaScript計(jì)算出現(xiàn)精度丟失問題的解決方法的文章就介紹到這了,更多相關(guān)JavaScript精度丟失內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
webpack4從0搭建組件庫(kù)的實(shí)現(xiàn)
這篇文章主要介紹了webpack4從0搭建組件庫(kù)的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11
JavaScript中鏈?zhǔn)秸{(diào)用之研習(xí)
方法鏈一般適合對(duì)一個(gè)對(duì)象進(jìn)行連續(xù)操作(集中在一句代碼)。一定程度上可以減少代碼量,缺點(diǎn)是它占用了函數(shù)的返回值。2011-04-04
JavaScript 關(guān)于元素獲取焦點(diǎn)(隱藏元素與div)
關(guān)于元素獲取焦點(diǎn)要注意2個(gè)小問題,需要的朋友可以參考下。2011-01-01
javascript 從if else 到 switch case 再到抽象
大家覺得在接手遺留代碼時(shí),見到什么東東是最讓人感到不耐煩的?復(fù)雜無比的 UML ?我覺得不是。2010-07-07
第九篇Bootstrap導(dǎo)航菜單創(chuàng)建步驟詳解
這篇文章主要介紹了Bootstrap導(dǎo)航菜單創(chuàng)建步驟詳解的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-06-06
JavaScript實(shí)現(xiàn)列出數(shù)組中最長(zhǎng)的連續(xù)數(shù)
這篇文章主要介紹了JavaScript實(shí)現(xiàn)列出數(shù)組中最長(zhǎng)的連續(xù)數(shù)的方法及使用,需要的朋友可以參考下2014-12-12
學(xué)習(xí)JavaScript設(shè)計(jì)模式(繼承)
這篇文章主要帶領(lǐng)大家學(xué)習(xí)JavaScript設(shè)計(jì)模式,其中重點(diǎn)介紹繼承,舉例說明為什么需要繼承,對(duì)繼承進(jìn)行詳細(xì)剖析,感興趣的小伙伴們可以參考一下2015-11-11
bootstrap select2插件用ajax來獲取和顯示數(shù)據(jù)的實(shí)例
今天小編就為大家分享一篇bootstrap select2插件用ajax來獲取和顯示數(shù)據(jù)的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-08-08

