js - 计算精度问题

文章讨论了JavaScript中浮点数的精度问题,由于底层表示机制,可能导致精度损失。提供了通过转换为浮点型计算,然后进行精度调整再转换回Number类型的解决方案,以及在类`Calc`中实现的加、减、乘、除运算的处理方法。
摘要由CSDN通过智能技术生成

Javascript作为一门大型编程语言,在日常开发中难免会涉及到大量的数学计算。然而,浮点数在计算过程中可能出现精度的问题,因此Javascript提供了一个高精度计算库来帮助处理复杂的数字计算。

计算机内部,使用的二进制浮点根本就不能准确地表示像 0.1, 0.2 或 0.3 这样的数字。

JavaScript 中的数字都是浮点数,即使看起来像整数的数字也是。这是因为 JavaScript 使用 IEEE 754 标准来表示数字,这种表示方法对于大多数情况是足够的,但在某些情况下可能导致精度丢失。

JavaScript 存储小数和其它语言如 Java 和 Python 都不同,JavaScript 中所有数字包括整数和小数都只有一种类型 即 Number类型 它的实现遵循 IEEE 754 标准,IEEE 754 标准的内容都有什么,这个咱不用管,我们只需要记住以下一点:

javascript以64位双精度浮点数存储所有Number类型值,即计算机最多存储64位二进制数。

解决方法:

Number(parseFloat(20.24*100).toPrecision(16))

存储二进制时小数点的偏移量最大为52位,最多可表示的十进制为9007199254740992,对应科学计数尾数是 9.007199254740992,这也是 JavaScript 最多能表示的精度。它的长度是 16,所以可以使用 toPrecision(16) 来做精度运算。

通过先转为浮点型计算,然后做精度运算后再转为Number类型即可

解决方案:

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);
    }
    
    /**
     * 计算小数位的长度
     * @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) { // 科学记数法
                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;
        }
    }
    
    /**
     * 将小数转成整数
     * @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;
    }
    
    /**
     * 确认我们的计算结果无误,以防万一
     * @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;
    }
}

总结

JavaScript 中的浮点数丢失精度问题是由底层表示方式引起的,因此在进行重要的精确计算时需要格外小心。选择合适的方法,如整数计算、使用专门的库或小数点后截断,可以帮助我们在实际应用中处理这些问题,确保得到精确的结果。在不同场景中选择适当的方法,是程序员需要谨慎考虑的问题,以避免潜在的错误。

  • 6
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
在Vue中解决JavaScript计算精度问题,可以使用第三方库math.js。通过引入math.js库,我们可以避免在浮点数计算中出现的精度问题。 在Vue中使用math.js可以按照以下步骤进行: 1. 首先,安装math.js库。可以使用npm或者yarn进行安装,例如:`npm install mathjs`。 2. 在Vue组件中引入math.js库,可以使用import语句进行引入,例如:`import math from 'mathjs'`。 3. 在需要进行计算的地方,使用math.js提供的相应方法进行计算。例如,对于减法,可以使用`math.subtract()`方法,对于加法,可以使用`math.add()`方法,对于除法,可以使用`math.divide()`方法。 4. 由于math.js处理的是大整数和大浮点数,所以在进行计算之前,需要将需要计算的数字转换成math.js支持的格式。可以使用`math.bignumber()`方法将数字转换成math.js支持的格式。 5. 最后,使用`math.format()`方法将计算结果转换成字符串格式,以避免出现科学计数法或多余的小数位。 举例来说,如果要解决0.1 + 0.2的计算精度问题,可以按照以下代码进行操作: ```javascript import math from 'mathjs' const addNumber = math.format(math.add(math.bignumber(0.1), math.bignumber(0.2))) console.log(addNumber) // 输出0.3 ``` 使用类似的方法,可以解决减法和除法的计算精度问题。请记住,在进行计算之前,将需要计算的数字转换成math.js支持的格式,然后使用相应的数学方法进行计算,并最后使用`math.format()`方法转换结果。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [vue使用mathjs,解决前端计算精度不足问题](https://blog.csdn.net/qq_43555948/article/details/119885043)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值