还在为js计算精度缺失迷茫?简单粗暴教你一招! | 8月更文挑战

822 阅读3分钟

前言

大多数语言在处理浮点数的时候都会遇到精度问题,但是在JS里似乎特别严重,比如: console.log(1.1-0.5); 结果居然是0.6000000000000001,加减乘除之类的都会有这个问题。

那这是js的错误吗?

今天大冰块就来好好谈谈这个问题。

二进制浮点运算

事实上,你的电脑做着正确的二进制浮点运算,但问题是你输入的是十进制的数,电脑以二进制运算,这两者并不是总是转化那么好的,有时候会得到正确的结果,但有时候就不那么幸运了。

alert(0.7+0.1)    // 输出0.7999999999999999
alert(0.6+0.2)    // 输出0.8

你输入两个十进制数,转化为二进制运算过后再转化回来,在转化过程中就会有损失。

但一般的损失往往在乘除运算中比较多,而JS在简单的加减法里也会出现这类问题,这个误差虽然非常小,但却是不该出现的。

有一种比较简单的逻辑,比如0.7+0.1,先把0.1和0.7都乘10,加完之后再除10。

按照这个逻辑,我们可以对加减乘除进行封装如下:

        (function () {
            var calc = {

                /*
                函数,加法函数,用来得到精确的加法结果 
                说明:函数返回较为精确的加法结果
                参数:arg1:第一个加数;arg2第二个加数;d要保留的小数位数(可以不传此参数,如果不传则不处理小数位数)
                调用:Calc.Add(arg1,arg2,d) 
                返回值:两数相加的结果,返回类型为string
                */
                Add: function (arg1, arg2, d) {
                    // 将传入的arg1和arg2转为字符串再进行切割,获取小数点的位数
                    let arg1Arr = arg1.toString().split("."), arg2Arr = arg2.toString().split("."), d1 = arg1Arr.length == 2 ? arg1Arr[1] : "", d2 = arg2Arr.length == 2 ? arg2Arr[1] : ""
                    d = typeof d === "number" ? parseInt(d) : Math.max(d1.length, d2.length) // 保留的小数位数等于 传入的d 或者 两个数的小数点后位数
                    let m = Math.pow(10, d) // 取10的maxLen次方
                    return Number(((arg1 * m + arg2 * m) / m).toFixed(d)) // 判断传入的d是否为number类型,是的话进行截取小数点后d位,不传就是undefined。
                },

                /*
                函数:减法函数,用来得到精确的减法结果 
                说明:函数返回较为精确的减法结果
                参数:arg1:第一个加数;arg2第二个加数;d要保留的小数位数(可以不传此参数,如果不传则不处理小数位数)
                调用:Calc.Sub(arg1,arg2,d) 
                返回值:两数相减的结果
                */
                Sub: function (arg1, arg2, d) {
                    return Calc.Add(arg1, -Number(arg2), d)
                },

                /*
                函数:乘法函数,用来得到精确的乘法结果 
                说明:函数返回较为精确的乘法结果。
                参数:arg1:第一个乘数;arg2第二个乘数;d要保留的小数位数(可以不传此参数,如果不传则不处理小数位数)
                调用:Calc.Mul(arg1,arg2,d) 
                返回值:两数相乘的结果 

                */
                Mul: function (arg1, arg2, d) {
                    d = typeof d === "number" ? parseInt(d) : (arg1.toString().split(".")[1] ? arg1.toString().split(".")[1].length : 0) + (arg2.toString().split(".")[1] ? arg2.toString().split(".")[1].length : 0)
                    return Number(arg1.toString().replace(".", "")) * Number(arg2.toString().replace(".", "")) / Math.pow(10, d) 
                },

                /*
                函数:除法函数,用来得到精确的除法结果
                说明:函数返回较为精确的除法结果。
                参数:arg1:除数;arg2被除数;d要保留的小数位数(可以不传此参数,如果不传则不处理小数位数)
                调用:Calc.Div(arg1,arg2,d) 
                返回值:arg1除于arg2的结果
                */
                Div: function (arg1, arg2, d) {
                    d = typeof d === "number" ? parseInt(d) : (arg2.toString().split(".")[1] ? arg2.toString().split(".")[1].length : 0) - (arg1.toString().split(".")[1] ? arg1.toString().split(".")[1].length : 0)
                    return Number(arg1.toString().replace(".", "")) / Number(arg2.toString().replace(".", "")) * Math.pow(10, d)
                }
            };
            window.Calc = calc;
        }());

该方法返回值是string类型,是因为:

toFixed(num)方法返回 NumberObject 的字符串表示,不采用指数计数法,小数点后有固定的 num 位数字。如果必要,该数字会被舍入,也可以用 0 补足,以便它达到指定的长度。如果 num 大于 le+21,则需调用 NumberObject.toString(),返回采用指数计数法表示的字符串。

参考文章:

www.iteye.com/blog/talent… blog.csdn.net/baidu_29701… blog.csdn.net/yexuan14/ar…

后记

你好哇,我是南极大冰块,一个技术与颜值成正比的前端工程师,崇尚一针见血的搞定前端问题,希望我的博客有帮助到了你。关注我,前端路途一起走。嘿哈~😛