js解决精度丢失的问题

简介:主要介绍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;
}

常用的几种浮点数操作就这么多了。

希望这篇文章可以帮到正在阅读的你,如果觉得此文对你有帮助,可以分享给你身边的朋友,同事,你关心谁就分享给谁,一起学习共同进步~~~

编程经验共享公众号二维码

编程经验共享公众号二维码
更多内容关注公众号
Copyright © 2021 编程经验共享 赣ICP备2021010401号-1