目录

一、计算精度问题的解决方案(解决加法、减法、乘法精度丢失问题)

1.1、解决原理

1.2、代码示例

二、为什么会出现计算精度的问题?(知其所以然)

2.1、二进制的存储原理

三、写在最后


一、计算精度问题的解决方案(解决加法、减法、乘法、除法精度丢失问题)

1.1、解决原理

JS在计算小数时会出现精度丢失的问题,但在处理整数时却不会,我们利用这一点来解决小数计算精度丢失的问题,我们先将数字转换为字符串,然后求出可以将小数变为整数的倍数,再对长度不一致的字符串进行补0,最后再除去相应的倍数得到没有损失精度的结果。

注:小数计算精度丢失的问题不仅存在于加减法,在乘除中同样存在,所以先将小数放大再除以相应倍数的方法是行不通的!

1.2、代码示例

function calc(num1, num2, calcStr)
{
	var str1, // 转换为字符串的数字
    str2,
    ws1 = 0,// ws1,ws2 用来存储传入的num的小数点后的数字的位数
    ws2 = 0,// 赋默认值,解决当整数和小数运算时倍数计算错误导致的结果误差 
    bigger,// bigger和smaller用于加,减,除法找出小的那个数字,给后面补0,解决位数不对从而造成的计算错误的问题;乘法需要将结果除两个数字的倍数之和
    smaller,// 例如:加减除法中1.001 + 2.03 ,如果不给2.03进行补0,最后会变成1001+203,数字错位导致结果错误;乘法中1.12*1.1会放大为112*11,所以结果需要除以1000才会是正确的结果,112*11/1000=1.232
    zeroCount, // 需要补充0的个数
    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;
	}
    // 如ws1 和 ws2 无默认值,如果num1 或 num2 不是小数的话则 ws1 或 ws2 的值将为 undefined 
    // bigger 和 smaller 的值会和预期不符
	bigger = ws1 > ws2 ? ws1 : ws2;
	smaller = ws1 < ws2 ? ws1 : ws2;
	
	switch (calcStr)
	{
		// 加减法找出小的那个数字,给后面补0,解决位数不对从而造成的计算错误的问题
		// 例如:1.001 + 2.03 ,如果不给2.03进行补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位无法准确的表达出一个小数时,就会产生补位动作,数值偏差就在这时产生了,这是造成计算精度问题的原因。

三、写在最后

本人水平有限,在文中可能有词不达意或错误之处,发出本文是想和大家探讨一下JS中小数计算的精度问题,如果本文对你产生了帮助,我会很开心☺。如果有不理解或其他建议欢迎私信交流讨论,感谢阅读!

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐