之前虽然有看到过 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方法,调用起来更加方便。
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>
下面的文章均来源于网络。
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在计算公式的时候,会出现误差,导致我们本来就应该正确的代码,出现了我们意想不到的结果。
例如:
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); }
但是有的时候,我们需要计算一连串的公式,并且里面包含了括号等等的复杂的符合运算,这个时候咱们应该怎么办呢?
例如:计算(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。同时也可以制作自己的网页计算器了。