JS中浮点数运算误差处理

先来个简单的代码片段:

> console.log(0.1 + 0.2)
> 0.30000000000000004

好奇怪的结果,怎么会是0.30000000000000004呢?难道是Javascript语言的bug还是chrome dev tools的bug?

 

其实,这不是语言的bug或者宿主环境的bug。目前所有的程序设计语言在对浮点数进行四则运算时,都会涉及到浮点数精确度的问题。

 

我们知道在计算机的世界中,计算机只认识0,1,我们传入的十进制数字并不会被计算机直接识别。计算机会先将其转换成二进制,然后使用转换后的二进制进行计算。

 

那么0.1和0.2转换成二进制分别是,

 

(0.1) => 0.0001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 101

(0.2) => 0.0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 01

然后对上面的两个二进制数字做加法,得到的结果是,

 

0.0100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1101 01

再把这个二进制转换成十进制,就是我们前面的结果0.30000000000000004了。

计算机世界的数值计算基本上都是这个过程,只不过C++、C#、Java这些传统强类型语言将这个边界问题封装在内部了,它们在进行浮点数四则运算时,会在语言层面自动解决这个问题。而Javascript作为一门弱类型的语言,它在语言层面并没有对这个问题进行处理,所以需要我们手动去处理。

 

那么,我们如何去避免这个问题呢?

 

基本上有两种思路。

 

先扩大数值到javascript可精确识别的精度级别(比如个位数级别)然后再进行计算,然后再对结果除以放大的倍数。

在运算的过程中通过toFixed()指定运算的精度要求。

比如我们要计算0.1 + 0.2,按照第一种思路,我们可以这么来做,

 

var a = 0.1;

var b = 0.2;

 

var ret = (a * 10 + b * 10) / 10;

console.log(ret);

这里,我们在计算之前,现将a和b乘以10(放大10倍,当然放大100、1000倍也是可以的),此时a和b已经不再是浮点数了,整数的四则运算当然是不需要考虑精度问题啦。最后我们还需要除以之前放大的倍数,得到正确的最终结果。其实这是一种迂回的办法来规避掉浮点数的四则运算精度问题。

 

按照第二种思路,我们可以这么来做,

 

var a = 0.1;

var b = 0.2;

 

var ret = (a + b).toFixed(1);

var ret = parseFloat(ret);

 

注意toFixed()返回的是一个String,所以我们还需要进行parseFloat或Number操作。

 

附上针对js浮点运算的公共方法:

var util = {

/**
* 加法优化,避免浮点误差
* @param arg1
* @param arg2
* @returns {String}
*/
calcAdd: function(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;
},

/**
* 减法优化,避免浮点误差
* @param arg1
* @param arg2
* @returns {String}
*/
calcSub: function accSub(arg1, arg2) {
var r1,r2,m,n;
try{r1=arg2.toString().split(".")[1].length}catch(e){r1=0}
try{r2=arg1.toString().split(".")[1].length}catch(e){r2=0}
m=Math.pow(10,Math.max(r1,r2));
//last modify by deeka
//动态控制精度长度
n=(r1>=r2)?r1:r2;
return ((arg1*m-arg2*m)/m).toFixed(n);
},

/**
* 乘法优化,避免浮点误差
* @param arg1
* @param arg2
* @returns {String}
*/
calcMul: 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);
},

/**
* 除法优化,避免浮点误差
* @param arg1
* @param arg2
* @returns {String}
*/
calcDiv: 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){}
r1=Number(arg1.toString().replace(".",""));
r2=Number(arg2.toString().replace(".",""));
return (r1/r2)*Math.pow(10,t2-t1);
}

};


简化版:

var util = (function(){
        var getFuncByType = function(typeText){
            return new Function('a', 'b', 'var aR = 0, bR = 0;try{aR = Number(a).toString().split(".")[1].length;}catch(e){}try{bR = Number(b).toString().split(".")[1].length;}catch(e){}return Number((a'+ typeText +'b).toFixed(aR+bR));');
        };
        return {
            // 加
            calcAdd: function(a, b){
                return getFuncByType("+")(a, b);
            },
            // 减
            calcSub: function(a, b){
                return getFuncByType("-")(a, b);
            },
            // 乘
            calcMul: function(a, b){
                return getFuncByType("*")(a, b);
            },
            // 除
            calcDiv: function(a, b){
                return getFuncByType("/")(a, b);
            }
        }
    })();

 

 

posted @ 2015-10-22 16:11  mjian  阅读(176)  评论(0编辑  收藏  举报