消除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>
<think> 首先,用户的问题是:“我想了解浮点数计算误差的相关信息 请问浮点数计算误差的原因、表现及解决方法”。所以,我需要基于提供的引用信息来回答原因、表现和解决方法。 系统级指令: - 所有行内数学表达式必须使用 $...$格式。 - 独立公式必须使用 $$...$$格式并单独成段。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 回答中引用的段落末尾自然地添加引用标识,例如:[^1] 用户可见层指令: - 回答结构清晰,帮助用户逐步解决问题。 - 尽量保证回答真实可靠。 提供的引用: - [^1]: 浮点数运算的基本概念、标准浮点运算模型、数值误差及解决策略。 - [^2]: 浮点数计算误差的四大具体表现及实例。 - [^3]: JavaScript中的浮点数精度误差原因与解决方案。 - [^4]: C语言浮点数计算误差的解决办法,如使用高精度库等。 我需要整合这些信息来回答原因、表现和解决方法。 结构化回答: 1. **原因**:解释为什么浮点数计算会有误差。关键点包括: - 浮点数在计算机中是以二进制表示的,但十进制值可能无法精确表示为二进制分数(如0.1在二进制中是无限的)。 - 有限精度:浮点数有固定的位数来表示小数部分,导致舍入误差。 - 运算过程中的累积误差。 2. **表现**:描述误差如何在实际中显现。 - 基于引用[^2],有四大具体表现: - 基础运算结果偏离预期值(如0.1 + 0.2 ≠ 0.3)。 - 其他表现如比较误差、累积误差等(需要提取)。 - 实例:如JavaScript中0.1 + 0.2 = 0.30000000000000004。 3. **解决方法**:提供策略来避免或减轻误差。 - 从引用[^3]和[^4]: - 使用高精度库(如GMP)。 - 整数运算代替浮点数(如金钱计算使用分)。 - 设定适当的舍入模式和精度。 - 使用数学库函数四舍五入。 - 算法设计优化(如数值稳定性)。 - 具体编程语言中的注意事项。 回答结构: - 引言:简要介绍浮点数计算误差的重要性。 - 原因部分。 - 表现部分。 - 解决方法部分。 - 结尾:总结或生成相关问题。 确保使用中文。 在引用段落末尾添加引用标识:自然地提及信息源并添加[^n]。 生成相关问题:在回答末尾,添加§§相关问题§§部分。 数学表达式格式: - 行内:如 $0.1 + 0.2 = 0.30000000000000004$ - 独立:如果需要公式,如浮点数表示。 可能的公式: - IEEE 754标准浮点数表示:$s \times m \times 2^e$,其中s是符号,m是尾数,e是指数。 但需简化,避免复杂。 最终输出: - 回答内容。 - §§相关问题§§部分。 </think> ### 浮点数计算误差的原因、表现及解决方法 浮点数计算误差在计算机科学中是一个基础性问题,尤其在科学计算、金融系统等对精度要求高的领域至关重要。浮点数在计算机中以二进制形式存储,由于二进制表示的有限精度和十进制值的无限特性,运算过程中不可避免会产生误差[^1]。这些误差可能累积,导致计算结果偏离预期。下面我将从原因、表现和解决方法三个方面,逐步分解这一主题。回答基于现有引用资料,确保内容真实可靠。 #### 一、浮点数计算误差的原因 浮点数运算误差主要源于计算机的底层表示方式和运算机制: 1. **二进制表示局限性**:计算机使用二进制浮点数(基于IEEE 754标准),但许多十进制小数(如0.1或0.2)无法精确表示为有限位二进制分数。例如,$0.1$ 在二进制中是无限循环小数($0.0001100110011..._2$),计算机只能截断或舍入到有限位存储,导致初始误差[^1][^3]。 2. **有限精度问题**:浮点数有固定尾数位(如单精度浮点数有23位尾数),运算时超出精度的部分会被丢弃或舍入。这会引起舍入误差(round-off error),尤其在连续运算中累积放大误差[^1][^2]。浮点数标准模型可表示为 $s \times m \times 2^e$,其中 $s$ 是符号位,$m$ 是尾数,$e$ 是指数,但有限的 $m$ 限制了精度范围[^1]。 3. **运算过程中的精度损失**:基本运算(加法、减法、乘法、除法)可能放大误差。例如,两个相近的数相减会导致有效数字丢失(称为抵消误差),或在除法时尾数对齐失败[^1][^4]。此外,运算顺序和算法设计不当也会引入数值不稳定性[^4]。 这些原因共同导致浮点数在看似简单的计算中出现偏差,如 $0.1 + 0.2 = 0.30000000000000004$,而非预期的 0.3[^3]。 #### 二、浮点数计算误差的表现 误差在实际编程中以多种形式呈现,引用[^2]将其总结为四大核心表现: 1. **基础运算结果偏离预期值**:最常见如加法或减法误差。例如,在JavaScript中执行 $0.1 + 0.2$ 会得出 $0.30000000000000004$,而非数学上的精确值[^2][^3]。类似地,$1.0 - 0.9 = 0.09999999999999998$ 也常见于多种编程语言。 2. **比较运算失败**:浮点数常导致等式比较出错。例如,在条件语句中 `if (0.1 + 0.2 == 0.3)` 可能返回 `false`,因为二进制表示差异使结果不相等[^2][^3]。 3. **累积放大误差**:在循环或迭代运算中,微小误差逐步累积。例如,求和序列如 $\sum_{i=1}^{1000} 0.1$ 可能不等于 100.0,而是 99.99999999999997,误差随迭代次数增加[^2][^4]。 4. **舍入和截断异常**:浮点数在转换或输出时出现意外值,如将 0.0000001 表示为指数形式 $1.0 \times 10^{-7}$,但实际操作中可能因精度限制被近似为 0.0,导致数据丢失[^2]。 这些表现在实际场景中影响广泛,例如在电商系统中价格计算错误或金融模型预测偏差,可能引发用户困惑或经济损失[^3]。 #### 三、浮点数计算误差的解决方法 解决策略需结合算法优化和编程实践,以减少或规避误差。以下是常见解决方案: 1. **使用高精度库**:针对关键计算,采用任意精度库如GMP(GNU Multiple Precision Arithmetic Library)。它能处理大整数或高精度小数,避免浮点数局限性。例如,在Python中可用 `decimal` 模块,或在C语言中集成GMP库,但需注意它可能增加计算复杂度和时间开销[^4]。 2. **整数运算替代**:在金钱计算等场景,改用整数表示值(如以“分”为单位代替“元”)。例如,计算 $0.1 + 0.2$ 时,先乘以100转为整数(10 + 20 = 30),再除以100输出为0.3。这能有效消除二进制误差[^4][^3]。 3. **设定舍入模式和精度**:通过编程控制浮点数行为: - 使用数学库函数四舍五入,如C语言中的 `round()`、`floor()` 或 `ceil()` 函数[^4]。 - 调整舍入模式:在C语言中用 `fesetround()` 设置舍入方向(如向零舍入或向最近整数舍入)。在JavaScript中,可用 `Number.toFixed()` 方法限定小数点位数[^3][^4]。 4. **算法优化设计**: - 优化运算顺序:避免大数和小数直接运算,以减少抵消误差。例如,计算 $\sum a_i$ 时,先将小值累加再处理大值。 - 数值稳定性处理:在复杂数学运算(如矩阵计算)中,选择稳定的算法以减少误差累积。预处理如缩放值范围(乘以大整数转为整数运算)[^4]。 5. **避免浮点数在关键应用**:在精度敏感领域(如金融),改用定点数或专用数据类型。同时,使用比较容差(tolerance):在等式比较时,允许小误差范围,如 `if (abs(a - b) < 1e-9)`[^3][^4]。 实施这些方法需根据场景权衡:高精度库适合科学计算,但整数替代更高效于日常编程。总体原则是预先评估误差风险,并在设计阶段融入精度控制[^1][^4]。 ### 总结 浮点数计算误差源于二进制表示的有限精度和运算机制,表现为基础运算偏差、比较失败、累积误差和舍入异常。解决方法包括使用高精度库、整数替代、舍入控制和算法优化。理解这些有助于开发可靠系统,减少数据错误。例如,在JavaScript或C语言中,简单调整即可显著提升精度[^2][^3][^4]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值