js精确计算:0.1 + 0.2 === 0.3 未必为true哦!

javascript避免数字计算精度误差的方法详解

 

本篇文章主要是对javascript避免数字计算精度误差的方法进行了介绍,需要的朋友可以过来参考下,希望对大家有所帮助

 

如果我问你 0.1 + 0.2 等于几?你可能会送我一个白眼,0.1 + 0.2 = 0.3 啊,那还用问吗?连幼儿园的小朋友都会回答这么小儿科的问题了。但是你知道吗,同样的问题放在编程语言中,或许就不是想象中那么简单的事儿了。
不信?我们先来看一段 JS。

var numA = 0.1;
var numB = 0.2;
alert( (numA + numB) === 0.3 );

执行结果是 false。没错,当我第一次看到这段代码时,我也理所当然地以为它是 true,但是执行结果让我大跌眼镜,是我的打开方式不对吗?非也非也。我们再执行以下代码试试就知道结果为什么是 false 了。

var numA = 0.1;
var numB = 0.2;
alert( numA + numB );

原来,0.1 + 0.2 = 0.30000000000000004。是不是很奇葩?其实对于浮点数的四则运算,几乎所有的编程语言都会有类似精度误差的问题,只不过在 C++/C#/Java 这些语言中已经封装好了方法来避免精度的问题,而 JavaScript 是一门弱类型的语言,从设计思想上就没有对浮点数有个严格的数据类型,所以精度误差的问题就显得格外突出。下面就分析下为什么会有这个精度误差,以及怎样修复这个误差。

首先,我们要站在计算机的角度思考 0.1 + 0.2 这个看似小儿科的问题。我们知道,能被计算机读懂的是二进制,而不是十进制,所以我们先把 0.1 和 0.2 转换成二进制看看:

0.1 => 0.0001 1001 1001 1001…(无限循环)
0.2 => 0.0011 0011 0011 0011…(无限循环)

双精度浮点数的小数部分最多支持 52 位,所以两者相加之后得到这么一串 0.0100110011001100110011001100110011001100110011001100 因浮点数小数位的限制而截断的二进制数字,这时候,我们再把它转换为十进制,就成了 0.30000000000000004。

原来如此,那怎么解决这个问题呢?我想要的结果就是 0.1 + 0.2 === 0.3 啊!!!

有种最简单的解决方案,就是给出明确的精度要求,在返回值的过程中,计算机会自动四舍五入,比如:

var numA = 0.1;
var numB = 0.2;
alert( parseFloat((numA + numB).toFixed(2)) === 0.3 );

但是明显这不是一劳永逸的方法,如果有一个方法能帮我们解决这些浮点数的精度问题,那该多好。我们来试试下面这个方法:

Math.formatFloat = function(f, digit) {
    var m = Math.pow(10, digit);
    return parseInt(f * m, 10) / m;
}

var numA = 0.1;
var numB = 0.2;

alert(Math.formatFloat(numA + numB, 1) === 0.3);

这个方法是什么意思呢?为了避免产生精度差异,我们要把需要计算的数字乘以 10 的 n 次幂,换算成计算机能够精确识别的整数,然后再除以 10 的 n 次幂,大部分编程语言都是这样处理精度差异的,我们就借用过来处理一下 JS 中的浮点数精度误差。

如果下次再有人问你 0.1 + 0.2 等于几,你可要小心回答咯!!

 

一种实现方法:
      //除法函数,用来得到精确的除法结果   
    //说明:javascript的除法结果会有误差,在两个浮点数相除的时候会比较明显。这个函数返回较为精确的除法结果。   
    //调用:accDiv(arg1,arg2)   
    //返回值:arg1除以arg2的精确结果  
      
    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);   
    }   
    }   
    //给Number类型增加一个div方法,调用起来更加方便。   
    Number.prototype.div = function (arg){   
    return accDiv(this, arg);   
    }   
      
      
    //乘法函数,用来得到精确的乘法结果   
    //说明:javascript的乘法结果会有误差,在两个浮点数相乘的时候会比较明显。这个函数返回较为精确的乘法结果。   
    //调用:accMul(arg1,arg2)   
    //返回值: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)   
    }   
    //给Number类型增加一个mul方法,调用起来更加方便。   
    Number.prototype.mul = function (arg){   
    return accMul(arg, this);   
    }   
      
      
    //加法函数,用来得到精确的加法结果   
    //说明:javascript的加法结果会有误差,在两个浮点数相加的时候会比较明显。这个函数返回较为精确的加法结果。   
    //调用:accAdd(arg1,arg2)   
    //返回值:arg1加上arg2的精确结果   
      
    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;//这句是有bug的,比如:69723829.76*100=6972382976.000001

    return (my_accMul(arg1,m) + my_accMul(arg2,m)) / m;
    }   
    //给Number类型增加一个add方法,调用起来更加方便。   
    Number.prototype.add = function (arg){   
    return accAdd(arg,this);   
    }  
      
    //在你要用的地方包含这些函数,然后调用它来计算就可以了。   
    //比如你要计算:7*0.8 ,则改成 (7).mul(8)   
    //其它运算类似,就可以得到比较精确的结果。  
      
      
      
    //减法函数  
    function Subtr(arg1,arg2){  
         var r1,r2,m,n;  
         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));  
         //last modify by deeka  
         //动态控制精度长度  
         n=(r1>=r2)?r1:r2;  
         return ((arg1*m-arg2*m)/m).toFixed(n);  
    }  

转载于:https://my.oschina.net/u/1052786/blog/910082

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值