一篇文章带您了解如何解决js计算精度误差


一、为什么JavaScript在计算中会出现精度误差

前言

其实在计算中经常会出现一些奇怪的现象,当我们使用JavaScript来计算 0.1 + 0.2,它得到的结果并不是我们期待的0.3,而是 0.30000000000000004

JavaScript计算

当然,如果你把它作为条件判断语句,你会发现一个和你想象中完全不一样的结果

js计算条件判断语句

这是究竟为什么呢?

其实在JavaScript中,计算可能出现误差的原因通常是由于浮点数的精度问题。JavaScript 中的Number类型采用IEEE 754标准来表示浮点数,这个标准定义的是双精度浮点数,它能够表示的数值范围非常广泛,而这个标准在处理一些特别大或特别小的数时会出现舍入错误,所以计算机无法准确表示的小数有 0.1、0.2 或 0.3 这样的数字,因为它使用的是二进制浮点格式

例如,当你尝试表示分数1/3时,由于3不是2的幂次方,JavaScript 无法精确地用双精度浮点数表示这个数值,结果会有舍入误差。

注:其实在很多编程语言,比如 C、C++、JavaScript 、Python、Java、Ruby 等,都会出现这个问题。

二、出现精度误差的原因

在JavaScript中的所有数字,无论是整数还是小数,都是以64位浮点数(double)形式存储。

浮点数的表示包括三部分:

  • 符号位(Sign):0表示正数,1表示负数。
  • 指数位(Exponent): r用科学计数法表示时的指数部分,即2的幂次方。
  • 尾数位(Mantissa): r用科学计数法表示时的尾数部分。

浮点数的计算过程大致为:

  1. 先将两个浮点数转换成同一个表示形式(即同一个指数)。
  2. 将两个数的尾数相加。
  3. 处理可能出现的溢出和下溢情况。

背后的原理

其实在我们日常生活中通常使用10进制来进行计算,我们会发现当10除以2或者5的倍数时才可以被精确的表示,例如1/2、1/4、1/5、1/8 和 1/10等。因为2和5是10的质因数。但是 1/3、1/6 和 1/7 则是无限循环的小数。

同理,在计算机系统中使用的是二进制。只有一个分数使用基数2的质因数来表示时,它才可以被精确地表示,因为2是2的唯一质因数

当我们尝试表示像 0.1 这样的十进制小数时,计算机会使用一个近似值。这个近似值是通过将无限循环的二进制小数转换为有限位数的浮点数表示来实现的。

因此,当我们在计算机中进行浮点数运算时,结果可能会有微小的误差。

例如,0.1 在二进制中的近似表示可能是 0.000110011001100…,但在计算机的浮点数表示中,它可能被截断或舍入为 0.00011001100110,这就导致了 0.1 + 0.2 在计算机中可能不等于 0.3,而是略微有所偏差。

三、如何解决JavaScript计算精度误差

  1. 避免浮点数计算,或者尽可能使用整数来进行计算,以下是一个简单的函数,用于加0.1和减0.1,确保精度。
function addOrSubtract(value: number, increment: number, precision: number) {
  const factor = Math.pow(10, precision);
  const newValue =
    (Math.round(value * factor) + Math.round(increment * factor)) / factor;
  return newValue;
}

// 使用示例
let num = 0.1;
num = addOrSubtract(num, 0.1, 1); // 加0.1
console.log(num); // 输出: 0.2

num = addOrSubtract(num, -0.1, 1); // 减0.1
console.log(num); // 输出: 0.1

这个函数通过precision参数指定需要保留的小数位数,内部先将数值和增量乘以precision位的因子(即10的precision次幂),进行整数运算后再除以相同的因子,以还原到正确的小数位数。

  1. 使用库,如decimal.js或bignumber.js。
// 使用npm安装
npm install decimal.js
// 在HTML中引入
<script src="https://cdnjs.cloudflare.com/ajax/libs/decimal.js/10.2.0/decimal.min.js"></script>
// 引入Decimal构造函数
const Decimal = decimal.Decimal;

// 创建两个Decimal对象
let a = new Decimal('0.1');
let b = new Decimal('0.2');

// 进行加法运算
let sum = a.plus(b);

// 输出结果
console.log(sum.toString()); // 输出: 0.3
  1. 使用toFixed()方法可以将数字格式化为指定小数位数的字符串,但这并不会改变数值本身,只是在显示时控制小数位数。
let sum = 0.1 + 0.2;
console.log(sum.toFixed(2)); // 输出: 0.3

总结

在我们的日常开发中,对于金融相关的计算,要特别注意。尽量使用整数,并在最后转换为小数进行展示。或者使用一些提供了处理大数和高精度计算的工具的库,来减少或消除浮点数计算的误差。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐