消除JS计算误差问题

使用背景

JS进行加减乘除计算时,经常会遇到计算结果不准确的情况,如:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

解决方法

1. toFixed()

该方法遵循四舍五入的原则。当四舍五入可以满足系统需要时,可使用;但如果对数字精度要求高,则不符合要求。

2. JavaScript 数字处理的第三方库:big.js(big.js文档跳转

big.js和bignumber.js、decimal.js都是同一个作者写的,都是用于JS的数字处理,但简单的消除加减乘除的计算误差使用big.js即可满足;
包的大小:big.js < bignumber.js < decimal.js;
bignumber.js 和 decimal.js 存储值的进制比 big.js 更高,因此当操作大量数字时,前两者的速度会更快。

big.js的使用

a. 使用npm安装依赖:npm install big.js
b. 在使用时引入:import Big from 'big.js';
c. 加法
注:最后的结果需要使用toString()转成字符串才可以正常显示,此时使用typeof数据类型是string,要转换成数字则可以使用Number(),此时使用typeof数据类型是number

<script>
	const x = new Big(0.1);// 0.1
    const y = x.plus(0.2); // 0.1 + 0.2
    const z = Big(0.7).plus(x).plus(y); // 0.7 + 0.1 + 0.3
    console.log(0.1 + 0.2);// 0.30000000000000004
    console.log(0.7 + 0.1 + 0.3);// 1.0999999999999999
    console.log(y.toString());// '0.3'
    console.log(typeof (y.toString()));// string
    console.log(Number(y.toString()));// 0.3
    console.log(typeof (Number(y.toString())));// number
    console.log(z.toString());// '1.1'
</script>

d. 减法

<script>
	 const x = new Big(0.3);
     const y = x.minus(0.1); // 0.3 - 0.1
     const z = Big(0.7).minus(x).minus(y); // 0.7 - 0.3 - 0.2
     console.log(0.3 - 0.1);// 0.19999999999999998
     console.log(y.toString());// '0.2'
     console.log(z.toString());// '0.2'
     console.log(typeof (z.toString()));// string
     console.log(Number(z.toString()));// 0.2
     console.log(typeof (Number(z.toString())));//number
</script>

e. 乘法
注意:位数太多或计算式中有科学计数法表示的数字,则结果会自动换算成科学计数法,若不想显示科学计数法需要自行转换

<script>
	const x = new Big(0.1);
    const y = x.times(0.2); // 0.1 * 0.2
    const m = Big('7e+500').times(x).times(y); // '7e+500' * 0.1 * 0.02
    const z = Big(0.05).times(x).times(y); // 0.05 * 0.1 * 0.02  
    console.log(0.1 * 0.2);// 0.020000000000000004
    console.log(y.toString());// '0.02'
    console.log(m.toString());// '1e-16'
    console.log(z.toString());// '0.0001'
    console.log(typeof (z.toString()));// string
    console.log(Number(z.toString()));// 0.0001
    console.log(typeof (Number(z.toString())));// number
</script>

f. 除法

<script>
	let x = new Big(0.3);
	let y = x.div(0.2); // 0.3 / 0.2
	let z = x.div(y).div(0.08); // 0.3 / 1.5 / 0.08
	console.log(0.3 / 0.2);// 1.4999999999999998
	console.log(y.toString());// '1.5'
	console.log(z.toString());// '2.5'
	console.log(typeof (z.toString()));// string
	console.log(Number(z.toString()));// 2.5
	console.log(typeof (Number(z.toString())));// number
</script>

g. big.js还可以进行比较大小、取绝对值、取模、四舍五入等运算,基本满足普通计算需求,详见big.js文档

补充:科学计数法转换成普通数字格式

<script>
getFullNum(num) {
	// 处理非数字
	if(isNaN(num)) { return num; };
	// 处理不需要转换的数字
	var str = '' + num;
	if(!/e/i.test(str)) { return num; };
	return (num).toFixed(18).replace(/\.?0+$/, '');
}
let num = 0.0000000000000003;
let result = this.getFullNum(num);//调用方法
console.log(0.0000000000000003);// 3e-16
console.log(result);// 0.0000000000000003
</script>

3. 数字升级

对符号两边要计算的数字先全部转化成整数(即升级,如乘以10的n次幂),再做对应的运算,对结果再进行降级(除以10的n次幂),得到最终的结果。

原理:整数的运算不会有误差

a. 加法和减法

<script>
	// 消除js计算误差:add是加数(减数),reduce是被加数(被减数),s是加减符号,n是指数
	formatNum(add, reduce, s, num) {
    	let m = Math.pow(10, num); // m是升级/降级的数
    	let res = s == '+' ? (add * m + reduce * m) / m : (add * m - reduce * m) / m;
    	return (res * m) / m;
	}
	console.log(this.formatNum(0.1, 0.2, '+', 10))
	console.log(this.formatNum(0.3, 0.2, '-', 10))
</script>

b. 乘法

<script>
	// arg1乘数, arg2被乘数
	accMul(arg1, arg2) {
	     let m = 0
	     let s1 = arg1.toString();//乘数转成字符串
	     let 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);//去掉小数点,相乘,然后降级相应的数量
	},
	console.log(this.accMul(0.2, 0.11))
</script>

c. 除法

<script>
	accDiv(arg1,arg2) {
	    let t1 = 0;
	    let t2 = 0;
	    let r1,r2;
	    try{ t1 = arg1.toString().split('.')[1].length; }catch(e) {}//
	    try{ t2 = arg2.toString().split('.')[1].length; }catch(e) {}
	    r1 = Number(arg1.toString().replace('.',''));
	    r2 = Number(arg2.toString().replace('.',''));//去掉小数点
	    if (t2 - t1 > 0) {
	        return (r1 / r2) * Math.pow(10,t2 - t1);
	    }else{
	        return (r1 / r2) / Math.pow(10,t1 - t2);
	    }
	}
	console.log(this.accDiv(0.03, 0.2))
</script>
### JavaScript 浮点数精度问题解决方案 JavaScript 中的浮点数精度问题源于其基于 IEEE 754 标准的 64 位浮点数表示法[^1]。由于这种表示方法无法精确存储某些十进制小数,因此在进行浮点数运算时可能会出现意外的结果。例如,`0.1 + 0.2` 的结果并非预期的 `0.3`,而是 `0.30000000000000004`。 为了解决这一问题,可以采用以下几种方法: #### 方法一:使用数学变换 通过将浮点数转换为整数进行计算,然后再转换回浮点数,可以有效避免精度误差。例如,可以通过将数值放大一定倍数(如乘以 100)来消除小数部分,完成计算后再缩小回去。 ```javascript function add(a, b) { const factor = 100; // 假设最多有两位小数 return (Math.round(a * factor) + Math.round(b * factor)) / factor; } console.log(add(0.1, 0.2)); // 输出 0.3 ``` 这种方法适用于小数位数固定的情况[^2]。 #### 方法二:利用第三方库 一些成熟的第三方库如 [decimal.js](https://github.com/MikeMcl/decimal.js/) 或 [big.js](https://github.com/MikeMcl/big.js/) 提供了高精度的数值计算能力。这些库能够处理任意精度的小数运算,避免原生浮点数的精度问题。 以下是一个使用 `decimal.js` 的示例: ```javascript const Decimal = require("decimal.js"); const result = new Decimal(0.1).plus(0.2).toNumber(); console.log(result); // 输出 0.3 ``` #### 方法三:格式化输出 如果精度问题仅影响显示而不影响逻辑,可以通过格式化输出来解决。例如,使用 `toFixed` 方法限制小数位数。 ```javascript const result = (0.1 + 0.2).toFixed(2); console.log(result); // 输出 "0.30" ``` 需要注意的是,`toFixed` 返回的是字符串类型,若需要数值类型可结合 `parseFloat` 使用。 ```javascript const result = parseFloat((0.1 + 0.2).toFixed(2)); console.log(result); // 输出 0.3 ``` #### 方法四:符号运算 对于简单的加减法运算,可以使用字符串拼接的方式避免浮点数精度问题。例如: ```javascript function add(a, b) { const aStr = a.toString(); const bStr = b.toString(); const aParts = aStr.split("."); const bParts = bStr.split("."); const maxDecimals = Math.max( aParts.length > 1 ? aParts[1].length : 0, bParts.length > 1 ? bParts[1].length : 0 ); return (parseFloat(a.toFixed(maxDecimals)) + parseFloat(b.toFixed(maxDecimals))).toFixed(maxDecimals); } console.log(add(0.1, 0.2)); // 输出 "0.30" ``` 这种方法适用于对精度要求较高的场景。 ### 注意事项 尽管上述方法可以有效缓解浮点数精度问题,但在涉及金融、科学计算等高精度需求的场景中,建议优先使用专门的数值计算库。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值