• Js四则运算精度问题处理


    JavaScript小数在做四则运算时,精度会丢失,这会在项目中引起诸多不便。先看个具体的例子:

     //较小的数运算
     console.log(0.09999999 + 0.00000001);  //0.09999999999999999
     console.log(-0.09999999 - 0.00000001); //-0.09999999999999999
     console.log(0.012345 * 0.000001); //1.2344999999999999e-8
     console.log(0.000001 / 0.0001); //0.009999999999999998
    
     //较大的数运算
     console.log(999999999 * 111111111); //111111110888888900

    从上面的结果可以看出,都不是正确的。为了解决浮点数运算不准确的问题,在运算前我们把参加运算的数先升级(10的X的次方)到整数,等运算完后再降级(0.1的X

    的次方)。具体的操作如下:

    /**
     * 四则运算
     *
     * @param x
     * @param y
     * @param op 操作符,0:加;1:减;2:乘;3:除
     * @param acc 保留小数位个数,进行四舍五入
     */
    function execute(x, y, op, acc) {
        var xx = Number(x == undefined ? 0 : x);
        var yy = Number(y == undefined ? 0 : y);
    
        //
        var a = science(xx);
        var b = science(yy);
    
        var na = a.r1;
        var nb = b.r1;
    
        var ta = a.r2;
        var tb = b.r2;
        var maxt = Math.max(ta, tb);
    
        //精度值处理
        var result = 0;
        switch (parseInt(op, 10)) {
            case 0: //
                result = (xx * maxt + yy * maxt) / maxt;
                break;
            case 1: //
                result = (xx * maxt - yy * maxt) / maxt;
                break;
            case 2:  //
                result = na * nb / (ta * tb);
                break;
            case 3: //
                result = na / nb * (tb / ta);
            default:
        }
    
        //小数位数处理
        if (acc) {
            return Number(Number(result).toFixed(parseInt(acc)));
        } else {
            return Number(result);
        }
    }
    /**
     * 将数值升级(10的X的次方)到整数
     */
    function science(num) {
        var re = {
            r1: 0, //数字去掉小数点后的值,也就是 r1*r2 的结果
            r2: 1 //小数部分,10的长度次幂
        };
        if (isInteger(num)) { //整数直接返回
            re.r1 = num;
            return re;
        }
        var snum = scienceNum(num + ""); //处理0.123e-10类似问题
        var dotPos = snum.indexOf("."); //小数点位置
        var len = snum.substr(dotPos + 1).length; //小数点长度
        re.r2 = Math.pow(10, len);
        re.r1 = parseInt(snum.replace(".", ""));
        return re;
    }
    /**
     * 将数值转为字符串
     * 
     * 通过移动小数点  扩大倍数或缩小倍数(解决出现e+、e-的问题)
     * 
     * JavaScript在以下情景会自动将数值转换为科学计数法:
     *   1)小数点前的数字多于21位。
     *   2)小数点后的零多于5个
     */
    function scienceNum(value) {
        if (!value) {
            return value;
        }
        if (typeof value === 'number') {
            value = value + ""
        };
        let eIndex = value.indexOf('E');
        if (eIndex == -1) {
            eIndex = value.indexOf('e')
        };
        if (eIndex != -1) {
            let doubleStr = value.substring(0, eIndex); //e前面的值
            let eStr = parseInt(value.substring(eIndex + 1, value.length)); //e后面的值
            let doubleStrArr = doubleStr.split('.');
            let doubleStr1 = doubleStrArr[0] || ""; 
            let doubleStr2 = doubleStrArr[1] || "";
    
            if (eStr < 0) { //e- 很小的数
                let str1Len = doubleStr1.length;
                let eStrs = Math.abs(eStr);
                if (str1Len > eStrs) {
                    let nums = doubleStr1.substring(0, eStrs);
                    let nume = doubleStr1.substring(eStrs, str1Len);
                    doubleStr = nums + "." + nume + nume;
                } else if (str1Len < eStrs) {
                    let indexNum = eStrs - str1Len;
                    let str = _makeZero(indexNum); //用0补齐
                    doubleStr = '0.' + str + doubleStr1 + doubleStr2;
                } else {
                    doubleStr = '0.' + doubleStr1 + doubleStr2;
                }
            } else {  //e+ 很大的数
                let str2Len = doubleStr2.length;
                if (str2Len > eStr) {
                    let _nums = doubleStr2.substring(0, eStr);
                    let _nume = doubleStr2.substring(eStr, str2Len);
                    doubleStr = doubleStr1 + _nums + '.' + _nume;
                } else if (str2Len < eStr) {
                    let _indexNum = eStr - str2Len;
                    let _str = _makeZero(_indexNum); //用0补齐
                    doubleStr = doubleStr1 + doubleStr2 + _str;
                } else {
                    doubleStr = doubleStr1 + doubleStr2;
                }
            }
            value = doubleStr;
        }
        return value;
    }
    //生成num个0的字符串
    function _makeZero(num) {
        var str = '';
        for (var i = 0; i < num; i++) {
            str += '0';
        }
        return str;
    }
    
    /**
     * 判断是否为整数,字符整数也返回true
     *
     * @param num
     * @returns
     */
    function isInteger(num) {
        return Math.floor(num) === Number(num);
    }

    为了调用方便,我们也可以增加如下几个方法:

    //加法运算
    function add(x, y, acc) {
        return execute(x, y, 0, acc);
    }
    
    //减法运算
    function subtract(x, y, acc) {
        return execute(x, y, 1, acc);
    }
    
    //乘法运算
    function multiply(x, y, acc) {
        return execute(x, y, 2, acc);
    }
    
    //除法运算
    function divide(x, y, acc) {
        return execute(x, y, 3, acc);
    }

    在上面的计算中,toFixed 方法默认采用四舍六入五成双算法。看个具体的例子:

        console.log(Number(9.8350).toFixed(2)); //9.84
        console.log(Number(9.8351).toFixed(2)); //9.84
        console.log(Number(9.8250).toFixed(2)); //9.82
        console.log(Number(9.82501).toFixed(2)); //9.83

    如果想改成四舍五入,可以重写该方法:

    /**
     * 默认toFixed方法为四舍六入五成双算法
     * 重写toFixed方法调整为四舍五入算法
     */
    Number.prototype.toFixed = function (d) {
        var s = this + "";
        if (!d) d = 0;
        if (typeof d == 'string') {
            d = Number(d);
        };
        if (s.indexOf(".") == -1) {
            s += ".";
        };
        s = scienceNum(s); //处理e+、e-情况
        s += new Array(d + 1).join("0");
        if (new RegExp("^(-|\+)?(\d+(\.\d{0," + (d + 1) + "})?)\d*$").test(s)) {
            var _s = "0" + RegExp.$2,
                pm = RegExp.$1,
                a = RegExp.$3.length,
                b = true;
            if (a == d + 2) {
                a = _s.match(/d/g);
                if (parseInt(a[a.length - 1]) > 4) {
                    for (var i = a.length - 2; i >= 0; i--) {
                        a[i] = parseInt(a[i]) + 1;
                        if (a[i] == 10) {
                            a[i] = 0;
                            b = i != 1;
                        } else break;
                    }
                }
                _s = a.join("").replace(new RegExp("(\d+)(\d{" + d + "})\d$"), "$1.$2");
            }
            if (b) {
                _s = _s.substr(1);
            };
            return (pm + _s).replace(/.$/, "");
        }
        return this + "";
    };

    测试一下:

        console.log(Number(9.8350).toFixed(2)); //9.84
        console.log(Number(9.8351).toFixed(2)); //9.84
        console.log(Number(9.8250).toFixed(2)); //9.83
        console.log(Number(9.82501).toFixed(2)); //9.83
  • 相关阅读:
    分享一个新软件 云端软件平台+个人使用心得
    扩展easyui.datagrid,添加数据loading遮罩
    在Flash中结合webservice 实现无缝滚动效果
    使用HttpWebRequest访问Web服务,并传递Cookie数据
    批量生成一组不同随机字符串
    文章分享代码
    jQuery.easyui与jQuery UI冲突
    转换BBSXP 2008到Discuz X1.5部分问题解决记录
    在ASP.NET的server form 中使用easyui的layout布局控件
    神奇3D圣诞树高超的技术!
  • 原文地址:https://www.cnblogs.com/xuwenjin/p/9035623.html
Copyright © 2020-2023  润新知