Js四则运算精度问题处理

Stella981
• 阅读 630

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
点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Easter79 Easter79
2年前
swap空间的增减方法
(1)增大swap空间去激活swap交换区:swapoff v /dev/vg00/lvswap扩展交换lv:lvextend L 10G /dev/vg00/lvswap重新生成swap交换区:mkswap /dev/vg00/lvswap激活新生成的交换区:swapon v /dev/vg00/lvswap
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Wesley13 Wesley13
2年前
Java获得今日零时零分零秒的时间(Date型)
publicDatezeroTime()throwsParseException{    DatetimenewDate();    SimpleDateFormatsimpnewSimpleDateFormat("yyyyMMdd00:00:00");    SimpleDateFormatsimp2newS
Wesley13 Wesley13
2年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
3个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这