简介:主要介绍js精度丢失的原因,js如何规避精度丢失的问题,使用decimal.js计算,自己如何封装计算函数解决精度丢失的问题
精度丢失问题想必大家都遇到过,你们又是如何解决的呢?
精度丢失的原因
由于浮点数的表示方式,某些数值无法精确表示。例如,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;
}
常用的几种浮点数操作就这么多了。
希望这篇文章可以帮到正在阅读的你,如果觉得此文对你有帮助,可以分享给你身边的朋友,同事,你关心谁就分享给谁,一起学习共同进步~~~