js加减乘除运算精度丢失问题

1,295 阅读4分钟
原因:

js 计算底层用的 是 IEEE 754 (64位二进制浮点数算术标准),精度上有限制, 无法准确表示十进制的浮点数, 就导致了精度丢失,可以使用一些库(big.js、bignumber.js 和 decimal.js,这三个库都是同一个作者写的)或者自定义函数来解决

三个库的对比

  • 包的大小
  1. big.js < bignumber.js < decimal.js
  2. big.js 是最小的任意精度的计算库。big.js 是三者中最小也最简单的,它只有 bignumber.js 一半的方法,不到 bignumber.js 的一半大。

功能

1、big.js
精度采用小数位;精度仅适用除法;4种舍入模式。
2、bignumber.js
配置选项;NaN;Infinity;精度采用小数位;精度仅适用于除法;随机数字;进制转换;9种舍入模式;模模式;模幂运算。
3、decimal.js
配置选项;NaN;Infinity;非整数次幂,exp,ln,log;三角函数;精度采用有效数字;所有操作均采取精度;随机数字;9种舍入模式;模模式;二进制,八进制,十六进制;二进制指数符号。

big.js、bignumber.js 和 decimal.js,这三个库都包含了 JavaScript 中 Number 类型的 toExponential、toFixed 和 toPrecision 方法。

性能

bignumber.js 和 decimal.js 存储值的进制比 big.js 更高,因此当操作大量数字时,前两者的速度会更快。

一、Decimal.js库

原理

  • 它的原理就是将数字用字符串表示,字符串在计算机中可以说是无限的。
  • 并使用基于字符串的算术运算,以避免浮点数运算中的精度丢失。它使用了一种叫做十进制浮点数算术(Decimal Floating Point Arithmetic)的算法来进行精确计算。
  • 具体来说,decimal.js库将数字表示为一个字符串,其中包含整数部分、小数部分和一些其他的元数据。它提供了一系列的方法和运算符,用于执行精确的加减乘除、取模、幂运算等操作。

引入

npm install --save decimal.js  // 安装
import Decimal from "decimal.js"  // 具体文件中引入
let a = 1
let b = 6 
// a 与 b 可以是 任何类型,Decimal 内部会自己处理兼容
// 下面两种都可以 可以带 new 也不可以不带 new
let res = new Decimal(a).add(new Decimal(b)) 
let res = Decimal(a).add(Decimal(b)) 
let a = "4"
let b = "8"
// a 与 b 可以是 任何类型,Decimal 内部会自己处理兼容
// 下面两种都可以 可以带 new 也不可以不带 new
let res = new Decimal(a).sub(new Decimal(b)) 
let res = Decimal(a).sub(Decimal(b)) 
let a = 1
let b = 6 
// a 与 b 可以是 任何类型,Decimal 内部会自己处理兼容
// 下面两种都可以 可以带 new 也不可以不带 new
let res = new Decimal(a).mul(new Decimal(b)) 
let res = Decimal(a).mul(Decimal(b)) 
let a = 1
let b = 6 
// a 与 b 可以是 任何类型,Decimal 内部会自己处理兼容
// 下面两种都可以 可以带 new 也不可以不带 new
let res = new Decimal(a).div(new Decimal(b)) 
let res = Decimal(a).div(Decimal(b)) 

注意

上面的结果是 一个 Decimal 对象,你可以转换成 Number 或则 String

let res = Decimal(a).div(Decimal(b)).toNumber()  // 结果转换成 Number
let res = Decimal(a).div(Decimal(b)).toString()  // 结果转换成 String

关于保存几位小数相关

//查看有几位小数 (注意不计算 小数点 最后 末尾 的 0)
y = new Decimal(987000.000)
y.sd()                                   // '3' 有效位数
y.sd(true)                               // '6' 总共位数

// 保留 多少个位数 (小数位 会补0)
x = 45.6
x.toPrecision(5)                         // '45.600'

// 保留 多少位有效位数(小数位 不会补0,是计算的有效位数)
x = new Decimal(9876.5)
x.toSignificantDigits(6)                 // '9876.5' 不会补0 只是针对有效位数

// 保留几位小数 , 跟 js 中的 number 一样
toFixed
x = 3.456
// 向下取整
x.toFixed(2, Decimal.ROUND_DOWN)  // '3.45' (舍入模式 向上0 向下1 四舍五入 4,7)
// 向上取整
Decimal.ROUND_UP 

//四舍五入
ROUND_HALF_UP

超过 javascript 允许的数字

如果使用超过 javascript 允许的数字的值,建议传递字符串而不是数字,以避免潜在的精度损失

new Decimal(1.0000000000000001); // '1'
new Decimal(88259496234518.57); // '88259496234518.56'
new Decimal(99999999999999999999); // '100000000000000000000'

new Decimal(2e308); // 'Infinity'
new Decimal(1e-324); // '0'

new Decimal(0.7 + 0.1); // '0.7999999999999999'

其它进制的数字

如果包含适当的前缀,则也接受二进制、十六进制或八进制表示法的字符串值。

x = new Decimal("0xff.f"); // '255.9375'
y = new Decimal("0b10101100"); // '172'
z = x.plus(y); // '427.9375'

z.toBinary(); // '0b110101011.1111'
z.toBinary(13); // '0b1.101010111111p+8'

x = new Decimal(
    "0b1.1111111111111111111111111111111111111111111111111111p+1023"
);
// '1.7976931348623157081e+308'

注意:

     在使用Decimal时,可以直接使用String()来初始化Decimal对象,使用字符串可以避免在js中发生精度损失。

Decimal 官方git

Decimal 官方api

Decimal 中文api