javascript浮点数的安全性

javascript浮点数的安全性

浮点数计算的不准确性

在js中我们经常会遇到这样的问题

1.2-1.0 > 0.199999999999999996

我们用分数来推到出浮点数

在十进制中,有许多数字不能用固定数字的十进制数字表示,例如1/3 = 0.3333333333 ……。

在二进制中,1/2 = 0.1,1 / 4 = 0.01,1 / 8 = 0.001等

.2等于2/10等于1/5,得到二进制分数0.001100110011001 …(无限)

浮点数只有32位或64位的精度,所以数字在某一点被切断,结果的数字是十进制的0.1999999999999996,而不是0.2。

总之,浮点数在计算机中是用二进制存储的,很多十进制的数字无法用二进制准确表示。

Number.EPSILON

ES6在Number对象上面,新增一个极小的常量Number.EPSILON。

Number.EPSILON
// 2.220446049250313e-16
Number.EPSILON.toFixed(20)
// '0.00000000000000022204'

引入一个这么小的量的目的,在于为浮点数计算,设置一个误差范围。
但是如果这个误差能够小于Number.EPSILON,我们就可以认为得到了正确结果。

0.1 + 0.2 - 0.3
// 5.551115123125783e-17

5.551115123125783e-17 < Number.EPSILON
// true

function withinErrorMargin (left, right) {
  return Math.abs(left - right) < Number.EPSILON;
}
withinErrorMargin(0.1 + 0.2, 0.3)
// true
withinErrorMargin(0.2 + 0.2, 0.3)
// false

解决方法

可以将浮点数,先转换为整数计算完之后,再处理计算后的数值。

加法

function accAdd(arg1,arg2){  
    var r1,r2,m;  
    try{r1=arg1.toString().split(".")[1].length}catch(e){r1=0}  
    try{r2=arg2.toString().split(".")[1].length}catch(e){r2=0}  
    m=Math.pow(10,Math.max(r1,r2))  
    return (arg1*m+arg2*m)/m  
}

减法

function accSub(arg1,arg2){      
    return accAdd(arg1,-arg2);  
}

乘法

function accMul(arg1,arg2)  
{  
    var m=0,s1=arg1.toString(),s2=arg2.toString();  
    try{m+=s1.split(".")[1].length}catch(e){}  
    try{m+=s2.split(".")[1].length}catch(e){}  
    return Number(s1.replace(".",""))*Number(s2.replace(".",""))/Math.pow(10,m)  
}

除法

function accDiv(arg1,arg2){  
    var t1=0,t2=0,r1,r2;  
    try{t1=arg1.toString().split(".")[1].length}catch(e){}  
    try{t2=arg2.toString().split(".")[1].length}catch(e){}  
    with(Math){  
        r1=Number(arg1.toString().replace(".",""))  
        r2=Number(arg2.toString().replace(".",""))  
        return (r1/r2)*pow(10,t2-t1);  
    }  
}

[参考资料]
ES6 标准入门
What Every JavaScript Developer Should Know About Floating Points
Why are floating point calculations so inaccurate
(完)

发表评论

电子邮件地址不会被公开。