解决JS中小数计算精度问题
目录
一、计算精度问题的解决方案(解决加法、减法、乘法、除法精度丢失问题)
1.1、解决原理
JS在计算小数时会出现精度丢失的问题,但在处理整数时却不会,我们利用这一点来解决小数计算精度丢失的问题,我们先将数字转换为字符串,然后求出可以将小数变为整数的倍数,再对长度不一致的字符串进行补0,最后再除去相应的倍数得到没有损失精度的结果。
注:小数计算精度丢失的问题不仅存在于加减法,在乘除中同样存在,所以先将小数放大再除以相应倍数的方法是行不通的!
1.2、代码示例
function calc(num1, num2, calcStr) { var str1, str2, ws1, ws2, bigger, smaller, zeroCount, isExistDot1, isExistDot2, sum, beishu = 1; // 将数字转换为字符串 str1 = num1.toString(); str2 = num2.toString(); // 是否存在小数点(判断需要计算的数字是不是包含小数) isExistDot1 = str1.indexOf(.) != -1 ? true : false; isExistDot2 = str2.indexOf(.) != -1 ? true : false; // 取小数点后面的位数 if (isExistDot1) { ws1 = str1.split(.)[1].length; } if (isExistDot2) { ws2 = str2.split(.)[1].length; } bigger = ws1 > ws2 ? ws1 : ws2; smaller = ws1 < ws2 ? ws1 : ws2; switch (calcStr) { // 加减法找出小的那个数字,给后面补0,解决位数不对从而造成的计算错误的问题 // 例如:1.001 + 2.03 ,如果不给2.02进行补0,最后会变成1001+203,数字错位导致结果错误 case "+": case "-": case "/": zeroCount = bigger - smaller; for(var i = 0; i < zeroCount; i++) { if (ws1 == smaller) { str1 += "0"; } else { str2 += "0"; } } break; case "*": // 乘法需要将结果除两个数字的倍数之和 bigger = bigger + smaller; break; default: return "暂不支持的计算类型,现已支持的有加法、减法、乘法、除法"; break; } // 去除数字中的小数点 str1 = str1.replace(., ); str2 = str2.replace(., ); // 计算倍数,例如:1.001小数点后有三位,则需要乘 1000 变成 1001,变成整数后精度丢失问题则不会存在 for (var i = 0; i < bigger; i++) { beishu *= 10; // 等价于beishu = beishu * 10; } num1 = parseInt(str1); num2 = parseInt(str2); // 进行最终计算并除相应倍数 switch (calcStr) { case "+": sum = (num1 + num2) / beishu; break; case "-": sum = (num1 - num2) / beishu; break; case "*": sum = (num1 * num2) / beishu; break; case "/": sum = num1 / num2; /* 除数与被除数同时放大一定倍数,不影响结果, 所以对数字进行放大对应倍数并进行补0操作后不用另对倍数做处理 */ break; default: return "暂不支持的计算类型,现已支持的有加法、减法、乘法、除法"; } return sum; }
二、为什么会出现计算精度的问题?(知其所以然)
2.1、二进制的存储原理
在JS中不区分整数和小数,因为JS中天生浮点数(双精度),在计算机存储中,双精度的实际存储位数是52位,由于二进制中只有 0 和 1,但52位有时并不能准确的表达小数点后面的数字,在十进制中有四舍五入,在二进制中存在0舍1入,所以当52位无法准确的表达出一个小数时,就会产生补位动作,数值偏差就在这时产生了,这是造成计算精度问题的原因。