转载

总结:那些年在JS中踩过Number.toFixed(n)的坑

一、引言

我相信很多小伙伴在js中写运算的时候对Number().toFixed(n)不陌生,一般正常数字进行加减乘除运算的时候很难发现问题,博主之前开发过财务软件,对于财务工作者而言,小数位的精确性尤为重要,保留小数时四舍五入出错可能都会导致一个会计崩溃!那么接下来跟着博主一起看一个Demo吧!

二、问题重现

博主为了使效果明显,以下数据是事先凑好的

计算中出现小数位很长

var a1 = '4.976';
var a2 = '3.2';

console.log("(Number(a1) + Number(a2) === "+(Number(a1) + Number(a2)));
console.log("(Number(a1) - Number(a2) === "+(Number(a1) - Number(a2)));
console.log("(Number(a1) * Number(a2) === "+(Number(a1) * Number(a2)));
console.log("(Number(a1) / Number(a2) === "+(Number(a1) / Number(a2)));

控制台输出结果如下:

(Number(a1) + Number(a2) === 8.176
(Number(a1) - Number(a2) === 1.7759999999999998
(Number(a1) * Number(a2) === 15.923200000000001
(Number(a1) / Number(a2) === 1.555

从上述结果我们不难发现在加法和除法运算中没有太大的问题,但是减法和乘法算数结果跟实际差了很多。这是为什么呢?博主特意去查阅了相关资料,首先我们应该知道计算机都是采用二进制存储数据的,但是像类似0.1这样的数据永远整除不了,一直0011无限循环
0.1二进制转换图
由于存储空间有限,计算机会舍弃后面的数值,所以机器中存储的就是一个近似值。

② 四舍六入五凑偶

来源百度百科:对于位数很多的近似数,当有效位数确定后,其后面多余的数字应该舍去,只保留有效数字最末一位,这种修约(舍入)规则是“四舍六入五成双”,也即“4舍6入5凑偶”,这里“四”是指≤4 时舍去,"六"是指≥6时进上,"五"指的是根据5后面的数字来定,当5后有数时,舍5入1;当5后无有效数字时,需要分两种情况来讲:
(1)5前为奇数,舍5入1;
(2)5前为偶数,舍5不进(0是偶数)。

理论说了那么多不如一个Demo看的清晰明了

console.log("(Number(a1) + Number(a2)).toFixed(2) === "+(Number(a1) + Number(a2)).toFixed(2));
console.log("(Number(a1) - Number(a2)).toFixed(2) === "+(Number(a1) - Number(a2)).toFixed(2));
console.log("(Number(a1) * Number(a2)).toFixed(2) === "+(Number(a1) * Number(a2)).toFixed(2));
console.log("(Number(a1) / Number(a2)).toFixed(2) === "+(Number(a1) / Number(a2)).toFixed(2));

控制台输出结果如下:

(Number(a1) + Number(a2)).toFixed(2) === 8.18
(Number(a1) - Number(a2)).toFixed(2) === 1.78
(Number(a1) * Number(a2)).toFixed(2) === 15.92
(Number(a1) / Number(a2)).toFixed(2) === 1.55

我们不难发现在进行除法计算后1.555保留两位小数时,机器给出的答案是1.55,按照四舍五入的思想,答案应该是1.56,这就出现上述描述的“四舍六入五凑偶”的现象,但是这样的计算给财务工作者们可是增添了不少负担。

三、解决办法

针对以上两种问题非自身代码原因导致的数据不准确难道就无解了吗?

答案当然是 NO ! NO ! NO !

博主也是查阅了许多经验人士的解决方案,经过大量的测试最终定了一版整合一个js文件,特分享给大家。

calculate.js文件下载

代码如下:

	/** 功能说明:加法 @param arg1 加数1,arg2 加数2 @return:精确值 **/
	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 
	}
	
	/** 功能说明:减法 @param arg1 被减数,arg2 减数 @return:精确值 **/
	function accSub(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); 
	}
   
   
	/** 功能说明:乘法 @param arg1 乘数1,arg2 乘数2 @return:精确值 **/
	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 被除数,arg2 除数 @return:精确值 **/
   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); 
	    } 
	}
   
	/** 功能说明:保留小数位数(四舍五入) @param number 需要保留的数字,n 保留位数 @return:保留位数后的数字 **/
	function accFixed (number, n) { 
		n = n ? parseInt(n) : 0; 
		if (n <= 0) return Math.round(number); 
		number = Math.round(number * Math.pow(10, n)) / Math.pow(10, n); 
		return number; 
	}

使用方法在jsp页面中根据你放置calculate.js文件的路径引用即可

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

看一下使用该方法后得出的结果吧

console.log("accAdd(a1,a2) === "+(accAdd(a1,a2)));
console.log("accSub(a1,a2) === "+(accSub(a1,a2)));
console.log("accMul(a1,a2) === "+(accMul(a1,a2)));
console.log("accDiv(a1,a2) === "+(accDiv(a1,a2)));

console.log("accFixed(accAdd(a1,a2),2)) === "+(accFixed(accAdd(a1,a2),2)));
console.log("accFixed(accSub(a1,a2),2)) === "+(accFixed(accSub(a1,a2),2)));
console.log("accFixed(accMul(a1,a2),2)) === "+(accFixed(accMul(a1,a2),2)));
console.log("accFixed(accDiv(a1,a2),2)) === "+(accFixed(accDiv(a1,a2),2)));

控制台输出结果:

accAdd(a1,a2) === 8.176
accSub(a1,a2) === 1.776
accMul(a1,a2) === 15.9232
accDiv(a1,a2) === 1.555
accFixed(accAdd(a1,a2),2)) === 8.18
accFixed(accSub(a1,a2),2)) === 1.78
accFixed(accMul(a1,a2),2)) === 15.92
accFixed(accDiv(a1,a2),2)) === 1.56
正文到此结束