zoukankan      html  css  js  c++  java
  • js精度误差

    之前虽然有看到过 js 精度相关的文章。但也都没有“印象深刻” ,但是今天"有幸"遇到了。

    做一个项目,进行页面调试的时候,

    当数量增加到3时总价格变得好长好长

    立马在控制台验证了一下,算出这么多个小数。

    还好之前有看过这方面的文章,知道是js的精度问题(但也不是js本身的问题,而是二进制的问题)。

     正确的应该是 239.7

    然后找了几篇文章,最后打算用那个插件。可以用字符串进行运算,所以功能很强呀。也不大,5k而已~什么?你给我说流量?优化?百度首页?淘宝?好吧,等有门做那样的项目时再说吧。

    想测试更多精度问题的问题试试:

    9007199254740992 + 1 // 丢失
    9007199254740992 + 2 // 未丢失
    9007199254740992 + 3 // 丢失
    9007199254740992 + 4 // 未丢失
    
    var x = 0.3 - 0.2; //30美分减去20美分
    var y = 0.2 - 0.1; //20美分减去10美分
    x == y; // =>false,两值不相等
    x == 0.1; // =>false,真实值为:0.09999999999999998
    y == 0.1; // =>true

    使用不同的函数分别计算+、-、*、/ :

    //问题比如:7*0.8 JavaScript算出来就是:5.6000000000000005
                //加法函数,用来得到精确的加法结果
                //说明: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
                }
                //用法:
                //给Number类型增加一个add方法,调用起来更加方便。
                Number.prototype.add = function (arg) {
                    return accAdd(arg, this);
                }
                //如:
                var t1 = 6.60;
                var t2 = 1.32;
                var t3 = 1.2;
                var t4 = 1.2;
                var t5 = 1.2;
                alert(Number(t1).add(Number(t2)).add(Number(t3)).add(Number(t4)).add(Number(t5)));
                //减法函数,用来得到精确的减法结果
                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);
                }
                //乘法函数,用来得到精确的乘法结果
                //说明: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的除法结果会有误差,在两个浮点数相除的时候会比较明显。这个函数返回较为精确的除法结果。
                //调用: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方法,调用起来更加方便。
    View Code

    JS中toFixed()方法的问题及解决方案

    四舍五入失效。

    《浮点数精度问题的前世今生?为什么计算机会 丢失小数……》

    http://www.zhoulujun.cn/zhoulujun/html/theory/computBase/2016_0714_7860.html

    1.335.toFixed(2) // 1.33

    重写 toFixed ,有人说重写后的返回值改变了。

    http://www.cnblogs.com/gushen/archive/2012/11/20/2778324.html

    <html>
        <head>
            <script type="text/javascript">
                Number.prototype.toFixed=function (d) { 
                    var s=this+""; 
                    if(!d)d=0; 
                    if(s.indexOf(".")==-1)s+="."; 
                    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+"";
    
                };
            </script>
        </head>
        <body>
            <input type="button" value="显示0.009.toFixed(2)" onclick="alert(0.009.toFixed(2))"><br />
            <input type="button" value="显示0.123.toFixed(2)" onclick="alert(0.123.toFixed(2))"><br />
            <input type="button" value="显示0.125.toFixed(2)" onclick="alert(0.125.toFixed(2))"><br />
            <input type="button" value="显示0.126.toFixed(2)" onclick="alert(0.126.toFixed(2))"><br />
            <input type="button" value="显示20.445.toFixed(2)" onclick="alert(20.445.toFixed(2))"><br />
            <input onclick="alert(20.405.toFixed(2))" type="button" value="显示20.405.toFixed(2)"> <br />
            <input onclick="alert(20.415.toFixed(2))" type="button" value="显示20.415.toFixed(2)"> <br />
            <input onclick="alert(20.425.toFixed(2))" type="button" value="显示20.425.toFixed(2)"> <br />
            <input onclick="alert(20.435.toFixed(2))" type="button" value="显示20.435.toFixed(2)"> <br />
            <input onclick="alert(20.445.toFixed(2))" type="button" value="显示20.445.toFixed(2)"> <br />
            <input onclick="alert(20.455.toFixed(2))" type="button" value="显示20.455.toFixed(2)"> <br />
            <input onclick="alert(20.465.toFixed(2))" type="button" value="显示20.465.toFixed(2)"> <br />
            <input onclick="alert(20.475.toFixed(2))" type="button" value="显示20.475.toFixed(2)"> <br />
            <input onclick="alert(20.485.toFixed(2))" type="button" value="显示20.485.toFixed(2)"> <br />
            <input onclick="alert(20.495.toFixed(2))" type="button" value="显示20.495.toFixed(2)"> <br />
            <input onclick="alert(0.05.toFixed(1))" type="button" value="显示0.05.toFixed(1)"> <br />
            <input onclick="alert(0.15.toFixed(1))" type="button" value="显示0.15.toFixed(1)"> <br />
            <input onclick="alert(0.25.toFixed(1))" type="button" value="显示0.25.toFixed(1)"> <br />
            <input onclick="alert(0.35.toFixed(1))" type="button" value="显示0.35.toFixed(1)"> <br />
            <input onclick="alert(0.45.toFixed(1))" type="button" value="显示0.45.toFixed(1)"> <br />
            <input onclick="alert(0.55.toFixed(1))" type="button" value="显示0.55.toFixed(1)"> <br />
            <input onclick="alert(0.65.toFixed(1))" type="button" value="显示0.65.toFixed(1)"> <br />
            <input onclick="alert(0.75.toFixed(1))" type="button" value="显示0.75.toFixed(1)"> <br />
            <input onclick="alert(0.85.toFixed(1))" type="button" value="显示0.85.toFixed(1)"> <br />
            <input onclick="alert(0.95.toFixed(1))" type="button" value="显示0.95.toFixed(1)"> <br />
        </body>
    </html>
    View Code

    下面的文章均来源于网络。 


     http://blog.csdn.net/forest_fire/article/details/50944339

    如果我问你 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 等于几,你可要小心回答咯!!


    1.因为计算机只认识二进制,所以某些数字二进制是无限循环的,例如:0.1=> 0.0001 1001 1001 ...无限循环   ,所以产生了精度问题,c这类语言已经封装好方法来避免,然而js并没有,为此带来不少的麻烦,特别是需要频繁计算的项目,出现bug还不容易发现。不扯皮,上解决方案:

    1.化零为整

    先把小数乘以10的次幂,然后再运算。

    0.1+0.2=>((0.1*10)+(0.2*10))/10=>0.3;

    当然这只是思路,实际应用还有很多问题,比如要判断有几位小数位,当表达式复杂的时候可阅读性的问题,我的思路是分别写加减乘除四个运算方法,把四个方法放到windwo对象的原型中(不推荐)或者放到某个模块类中;

    2.CalcEval.js引擎

    不想动脑的福利来了,CalcEval引擎专门解决js精度问题。

    引入CalcEval.js

    <script src="js/CalcEval.js"></script>

    var ce=new CalcEval();//创建引擎对象

    var result=ce.eval('0.1+0.2');//注意:表达式必须以字符串的形式传入


     
    javascript公式计算引擎-解决浮点数计算误差-网页计算器
     

    我们大家都知道,javascript在计算公式的时候,会出现误差,导致我们本来就应该正确的代码,出现了我们意想不到的结果。

    例如:

    45.6*13=592.8000000000001(结果应该是592.8);
    0.7+0.1=0.7999999999999999(应该是0.8);
    //还有N多,在此不一一列举。

    网上有一个比较认可的解决方法,就是自己去写加法,减法,乘法,除法。

    例如:

    // 两个浮点数求和
        function accAdd(num1,num2){
           var r1,r2,m;
           try{
               r1 = num1.toString().split('.')[1].length;
           }catch(e){
               r1 = 0;
           }
           try{
               r2=num2.toString().split(".")[1].length;
           }catch(e){
               r2=0;
           }
           m=Math.pow(10,Math.max(r1,r2));
           // return (num1*m+num2*m)/m;
           return Math.round(num1*m+num2*m)/m;
        }
        
        // 两个浮点数相减
        function accSub(num1,num2){
           var r1,r2,m;
           try{
               r1 = num1.toString().split('.')[1].length;
           }catch(e){
               r1 = 0;
           }
           try{
               r2=num2.toString().split(".")[1].length;
           }catch(e){
               r2=0;
           }
           m=Math.pow(10,Math.max(r1,r2));
           n=(r1>=r2)?r1:r2;
           return (Math.round(num1*m-num2*m)/m).toFixed(n);
        }
        // 两数相除
        function accDiv(num1,num2){
           var t1,t2,r1,r2;
           try{
               t1 = num1.toString().split('.')[1].length;
           }catch(e){
               t1 = 0;
           }
           try{
               t2=num2.toString().split(".")[1].length;
           }catch(e){
               t2=0;
           }
           r1=Number(num1.toString().replace(".",""));
           r2=Number(num2.toString().replace(".",""));
           return (r1/r2)*Math.pow(10,t2-t1);
        }
        
        function accMul(num1,num2){
           var m=0,s1=num1.toString(),s2=num2.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);
        }
    View Code

    但是有的时候,我们需要计算一连串的公式,并且里面包含了括号等等的复杂的符合运算,这个时候咱们应该怎么办呢?

    例如:计算(0.7+0.1)÷(45.6*13)

    这样的公式,我们是无法通过上面的自定义函数来解决的。因此今天给大家介绍一个比较好的计算引擎。

    CalcEval.js

    CalcEval引擎是一个专门解决javascript浮点数误差的的引擎,能够完美的解决各种复合的运算,最终输出正确的结果。

    使用方法:

    第一步:引入CalcEval.js

    <script type="text/javascript" src="CalcEval.js"></script>

    第二部:在页面上调用CalcEval的解析引擎入口

    var ce = new CalcEval();//创建引擎对象
    var result = ce.eval("(0.7+0.1)/(45.6*13)");//调用引擎接口来解析公式的字符串,这个地方,必须要将公式以字符串的形式传入。
    alert(result);//查看返回结果。

    就这么简单的过程,就可以解决了每个浏览器中的浮点数计算bug。同时也可以制作自己的网页计算器了。

  • 相关阅读:
    Paint类的介绍
    缓存淘汰算法之LRU
    Android SurfaceView实战 打造抽奖转盘
    android中scrollTo和scrollBy的理解
    Android View.onMeasure方法的理解
    Android Context 上下文 你必须知道的一切
    Android Animation简述
    Markdown 语法说明
    理解Java虚拟机体系结构
    Java集合框架:HashMap
  • 原文地址:https://www.cnblogs.com/daysme/p/6547492.html
Copyright © 2011-2022 走看看