精度丢失問題想必大家都遇到過,你們又是如何解決的呢?
精度丢失的原因
由于浮點數的表示方式,某些數值無法精确表示。例如,0.1 在二進制中是一個無限循環小數,是以在計算機中表示為一個近似值。
當數字超過JavaScript中的安全整數範圍(數值的精度隻能到 53 個二進制位)時,計算結果可能不準确。例如:
console.log(9007199254740992 + 1);
還有其他一些原因,導緻js無法精确的計算浮點數。
為了解決此問題,在網上搜尋了一番,大緻的計算方式有如下幾種:
使用整數進行計算
将浮點數轉換為整數,然後進行計算,最後再将結果轉換回浮點數。例如,将兩個浮點數相乘時,可以将它們分别乘以一個相同的倍數(例如10的幂),使其變為整數,然後進行整數相乘,最後再除以相應的倍數。
具體代碼如下:
function multiply(a, b) {
const multiplier = 10000; // 選擇一個合适的倍數,如10000
const intA = a * multiplier;
const intB = b * multiplier;
return (intA * intB) / (multiplier * multiplier);
}
自己以前也用過類似的方法進行封裝,但是發現,很多時候還是會導緻精度丢失,這種方法不建議大家使用
使用第三方庫
有一些第三方庫專門用于處理浮點數計算精度問題,如 decimal.js、bignumber.js 等。這些庫提供了一系列方法來進行高精度的浮點數計算。
第三方封裝的還不錯,不會有精度丢失的問題。具體代碼如下:
import Decimal from 'decimal.js';
const a = new Decimal(0.1);
const b = new Decimal(0.2);
const result = a.plus(b).toNumber();
安裝decimal.js:npm install --save decimal.js
官方文檔:http://mikemcl.github.io/decimal.js/
自己封裝的函數
自己最開始隻是簡單将倆個數相乘程式設計整數再進行操作,但是bug依然很多,計算精度有問題。
具體代碼如下:
new Vue({
el:'#app',
data(){
return {
number_a:'',
number_b:'',
result:'',
result1:''
}
},
methods:{
decimalNum(){
this.result1 = (parseInt(this.number_a*1000)+parseInt(this.number_b*1000))/1000
}
}
});
假設 number_a=1.23154 ,number_b=3.2156 計算的結果:4.446,但是實際應該是:4.447
因為第一步浮點數轉整數時已經舍去最後面的兩位,導緻最終的結算結果有誤。根據這個原因,修改第一步功能并重新封裝成如下:
new Vue({
el:'#app',
data(){
return {
number_a:'',
number_b:'',
result:'',
result1:''
}
},
methods:{
//解析浮點數資訊
parseNum(num){
num = num+'';
let numArr = num.split('.')
if(!numArr[1]) numArr[1] = '';
return numArr;
},
getScaleNum(scale){
let scaleNum = '1';
for (let i=0;i<scale;i++){
scaleNum += '0';
}
return scaleNum*1;
},
//兩個任意精度的數字相加,num1,num2:為源數字 scale:為保留小數點後多少位
bcAdd(num1,num2,scale){
num1 = this.parseNum(num1);
num2 = this.parseNum(num2);
let maxLength = num1[1].length > num2[1].length ? num1[1].length : num2[1].length;
let str1 = num1[0];
let str2 = num2[0];
let floatNum = '1';
for (let i=0;i<maxLength;i++){
str1 += (num1[1][i] ? num1[1][i] : '0');
str2 += (num2[1][i] ? num2[1][i] : '0');
floatNum += '0';
}
floatNum = floatNum*1;
let num = str1 * 1 + str2 * 1;
let scaleNum = this.getScaleNum(scale);
return parseInt( (num / floatNum)*scaleNum ) / scaleNum;
},
decimalNum(){
this.result = this.bcAdd(this.number_a,this.number_b,3);
}
}
});
這種方式基本就解決了因截取導緻計算精度丢失的問題,減法及乘法也是如此:
//兩個任意精度的數字相減
bcsub(num1,num2,scale){
num1 = this.parseNum(num1);
num2 = this.parseNum(num2);
let maxLength = num1[1].length>num2[1].length?num1[1].length : num2[1].length;
let str1 = num1[0];
let str2 = num2[0];
let floatNum = '1';
for (let i=0;i<maxLength;i++){
str1 += (num1[1][i] ? num1[1][i] : '0');
str2 += (num2[1][i] ? num2[1][i] : '0');
floatNum += '0';
}
floatNum = floatNum*1;
let num = str1 * 1 - str2 * 1;
let scaleNum = this.getScaleNum(scale);
return parseInt( (num / floatNum)*scaleNum ) / scaleNum;
},
//兩個任意精度的數字相乘
bcmul(num1,num2,scale){
num1 = this.parseNum(num1);
num2 = this.parseNum(num2);
let maxLength = num1[1].length>num2[1].length?num1[1].length : num2[1].length;
let str1 = num1[0];
let str2 = num2[0];
let floatNum = '1';
for (let i=0;i<maxLength;i++){
str1 += (num1[1][i] ? num1[1][i] : '0');
str2 += (num2[1][i] ? num2[1][i] : '0');
floatNum += '0';
}
floatNum = floatNum*1;
let num = str1 * str2;
let scaleNum = this.getScaleNum(scale);
return parseInt( (num / floatNum/floatNum)*scaleNum ) / scaleNum;
},
但是在計算除法時,此算法就不能使用了,由于除法有可能無法除盡,是以在使用除法時隻能使用源資料相除後再保留想要的位數,具體代碼如下:
//兩個任意精度的數字相除
bcdiv(num1,num2,scale){
let scaleNum = this.getScaleNum(scale);
return parseInt((num1/num2)*scaleNum)/scaleNum;
}
常用的幾種浮點數操作就這麼多了。
希望這篇文章可以幫到正在閱讀的你,如果覺得此文對你有幫助,可以分享給你身邊的朋友,同僚,你關心誰就分享給誰,一起學習共同進步~~~