解决前端JS精度丢失的问题
2024-11-04 13:36:13

前端数据精度丢失是个常见的问题,本次记录说实话有点大可不必,毕竟这是一个前端常见的问题。

但是考虑到自己的记忆水平不够,每次再去找攻略又有点不太合适,太过浪费时间。

索性这次就记录一下,便于后续快速使用。

正文

在前端开发中,尤其是在处理财务数据或需要高精度计算的场景下,JavaScript 的浮点数运算可能会导致精度丢失。

例如,0.1 + 0.2 的结果不是 0.3,而是 0.30000000000000004

原因分析

  • JavaScript 使用 IEEE 754 标准来表示浮点数,这种表示方法在某些情况下会导致精度丢失。
  • 浮点数的二进制表示无法精确表示某些十进制小数,从而导致计算结果不准确。

应用场景

我知道有很多可能为了方便会用使用 toFixedMath.round ,对于简单的应用场景,这可能已经足够。

但是大多数我们要处理精度的场景,往往都是涉及到支付金额的计算,这些都是非常重要的场景,一定不能出现精度丢失的情况。

所以,此时我们就需要引入精度库。

市面上有常见的好几种精度库,我这里距离如下:

  • decimal.js:功能最全面,适合大多数高精度计算需求。
  • big.js:体积最小,适合对性能和体积有严格要求的项目。
  • bignumber.js:功能强大,支持多种数据类型,适合复杂的高精度计算场景。

以上是市面上常用的三种精度库,如果你想了解更多,欢迎参考下放链接。

big.js、bignumber.js 和 decimal.js 之间的差别(翻译)

decimal.js

我这里就先介绍我自己常用的精度库,泛用性最广,使用起来也相对简单的库。

1
npm install decimal.js

Decimal.js 支持以下运算符的重载

运算符名称 运算符
加法 +
减法 -
乘法 *
除法 /
取模运算 %
指数运算 **
自增运算 ++
自减运算

这些运算符被重载后,可以直接用于 Decimal 对象之间的运算,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
const a = new Decimal('2.5');
const b = new Decimal('3');

const c = a + b; // 等同于 a.plus(b)
const d = b - a; // 等同于 b.minus(a)
const e = a * b; // 等同于 a.times(b)
const f = a / b; // 等同于 a.div(b)
const g = b % a; // 等同于 b.mod(a)
const h = a ** 2; // 等同于 a.toPower(2)

let i = new Decimal('0');
i++; // 等同于 i = i.plus(1);
i--; // 等同于 i = i.minus(1);

以下是它的通常用法:

格式化数值

数值被处理后,不能直接展示到页面,得格式化一下,一般常用的由以下几种。

  • toString() 格式化为字符串。
  • valueOf() 格式化为字符串,但是有符号零 console.log((new Decimal(-0)).valueOf()) // -0
  • toNumber() 格式化为Number类型,转换为原始数字的值。
  • toFixed() 格式化为字符串类型,用法和JS中toFixed()一样,不同的是decimal.js中的toFixed()有第二参数,可以设置舍入的类型。

还有一些不常用的

  • toBinary() 格式化为二进制。
  • toHexadecimal() 格式化为十六进制。
  • toOctal() 格式化为八进制。

加减乘除

在计算中最经常用到就是加减乘除,下面来看一下decimal.js中的加减乘除。

有两种用法,一种是使用Decimal类的静态方法,一种是使用Decimal类实例方法。

  • 加法
1
2
3
4
Decimal.add(1, 2); // 3

const x = new Decimal(1);
const result = x.plus(2); // 3
  • 减法
1
2
3
4
Decimal.sub(3, 1); // 2

const x = new Decimal(3);
const result = x.sub(1); // 2
  • 乘法
1
2
3
4
Decimal.mul(3, 2); // 6

const x = new Decimal(3);
const result = x.mul(2); // 6
  • 除法
1
2
3
4
Decimal.div(6, 2); // 3

const x = new Decimal(6);
const result = x.div(2); // 3

使用除法时候要注意 除数(分母)不能为0。

取绝对值

1
2
3
4
Decimal.abs(-3); // 3

const x = new Decimal(-3);
const result = x.abs(); // 3

比较

  • 大于
1
2
3
const x = new Decimal(2);
const y = new Decimal(1);
console.log(x.greaterThan(y)); // true
  • 大于等于
1
2
3
const x = new Decimal(2);
const y = new Decimal(2);
console.log(x.greaterThanOrEqualTo(y)); // true
  • 小于
1
2
3
const x = new Decimal(2);
const y = new Decimal(1);
console.log(y.lessThan(x)); // true
  • 小于等于
1
2
3
const x = new Decimal(2);
const y = new Decimal(2);
console.log(y.lessThanOrEqualTo(x)); // true
  • 等于
1
2
3
const x = new Decimal(2);
const y = new Decimal(2);
console.log(x.equals(y)); // true

判断

  • 是否是整数
1
2
3
4
const x = new Decimal(1) 
x.isInteger(); // true
const y = new Decimal(123.456)
y.isInt(); // false
  • 是否是正数
1
2
3
4
x = new Decimal(0);
x.isPositive();// true
y = new Decimal(-2);
y.isPos(); // false
  • 是否是负数
1
2
3
4
x = new Decimal(-0); 
x.isNegative(); // true
y = new Decimal(2);
y.isNeg(); // false

结语

有传言说这三种精度库是一个作者,其实用哪个都可以。

但是这个传言我记不清自己是在哪里看到的了,不过这里我就不去详细考证了。

于此时的我而言,能用就行。

参考

js 中使用 decimal.js 进行不丢失精度的小数计算 - 空明流光 - 博客园