前言
众所周知的 JavaScript
二进制精度问题,浮点数的计算精度会存在缺失问题。最经典的例子就是为什么0.1+0.2 !== 0.3
一句话概括就是:
ECMAScript
规范定义Number
的类型遵循了IEEE754-2008
中的64位浮点数规则定义的小数后的有效位数至多为52位导致计算出现精度丢失问题!
不过网上已经有很多专门的类库可以解决这个问题。
如果你在工作中遇到问题、在面试中遇到疑惑、在前端路上遇到了阻碍,都可以加入我们前端有道 Family,我会竭尽全力为大家答疑解惑,让我们共同努力,一同成长。
点击添加技术交流群或者关注公众号🎨前端有道
目录
原生封装
加
/**
** 加法函数,用来得到精确的加法结果
** 说明:javascript的加法结果会有误差,在两个浮点数相加的时候会比较明显。这个函数返回较为精确的加法结果。
** 调用:accAdd(arg1,arg2)
** 返回值:arg1加上arg2的精确结果
**/
function accAdd(arg1, arg2) {
let r1, r2, m
try {
r1 = arg1.toString().split('.')[1].length
} catch (e) {
r1 = 0
}
try {
r2 = arg2.toString().split('.')[1].length
} catch (e) {
r2 = 0
}
m = Math.pow(10, Math.max(r1, r2))
return (arg1 * m + arg2 * m) / m
}
减
/**
** 减法函数,用来得到精确的减法结果
** 说明:javascript的减法结果会有误差,在两个浮点数相减的时候会比较明显。这个函数返回较为精确的减法结果。
** 调用:accSub(arg1,arg2)
** 返回值:arg1加上arg2的精确结果
**/
function accSub(arg1, arg2) {
var r1, r2, m, n;
try {
r1 = arg1.toString().split(".")[1].length;
} catch (e) {
r1 = 0;
}
try {
r2 = arg2.toString().split(".")[1].length;
} catch (e) {
r2 = 0;
}
m = Math.pow(10, Math.max(r1, r2)); //last modify by deeka //动态控制精度长度
n = r1 >= r2 ? r1 : r2;
return ((arg1 * m - arg2 * m) / m).toFixed(n);
}
乘
/**
** 乘法函数,用来得到精确的乘法结果
** 说明:javascript的乘法结果会有误差,在两个浮点数相乘的时候会比较明显。这个函数返回较为精确的乘法结果。
** 调用:accMul(arg1,arg2)
** 返回值:arg1乘以 arg2的精确结果
**/
function accMul(arg1, arg2) {
let m = 0
let s1 = arg1.toString()
let s2 = arg2.toString()
try {
m += s1.split('.')[1] ? s1.split('.')[1].length : ''
} catch (e) {}
try {
m += s2.split('.')[1] ? s2.split('.')[1].length : ''
} catch (e) {}
return (Number(s1.replace('.', '')) * Number(s2.replace('.', ''))) / Math.pow(10, m)
}
除
/**
** 除法函数,用来得到精确的除法结果
** 说明:javascript的除法结果会有误差,在两个浮点数相除的时候会比较明显。这个函数返回较为精确的除法结果。
** 调用:accDiv(arg1,arg2)
** 返回值:arg1除以arg2的精确结果
**/
function accDiv(arg1, arg2) {
let t1 = 0
let t2 = 0
let r1
let 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('.', ''))
return (r1 / r2) * Math.pow(10, t2 - t1)
}
封装
定义一个函数来调用加减乘除方法,这样做有个好处,用到地方调用加减乘除方法一致,假设某个方法后面发现那个库更好用或者某个平台不兼容、算法不太严谨、扩展新的功能等等,我们只要维护这个函数就行,不用在考虑项目中某个组件单独引用,没有按照这个规范因为这次维护引发的新问题。
export const calcFn = {
add() {
const arg = Array.from(arguments)
return arg.reduce((total, num) => {
return accAdd(total, num)
})
},
sub() {
const arg = Array.from(arguments)
return arg.reduce((total, num) => {
return accSub(total, num)
})
},
mul() {
const arg = Array.from(arguments)
return arg.reduce((total, num) => {
return accMul(total, num)
})
},
divide() {
const arg = Array.from(arguments)
return arg.reduce((total, num) => {
return accDiv(total, num)
})
}
}
big.js
- 介绍:任意精度十进制算术的小型、快速、易于使用的库。
- 特性:目前同类型最小包、无依赖、包大小
3 KB
、兼容ECMAScript 3+
可以说适用于所有浏览器。 - 官网:GitHub
安装使用
浏览器
<script src='https://cdn.jsdelivr.net/npm/big.js@6.1.1/big.min.js'></script>
Node.js
npm install big.js
使用
x = new Big(0.1)
y = new Big(0.2)
z = new Big(0.3)
x.plus(y).eq(z) // true
运算符操作函数
以下big.js
目前支持运算符操作函数。
abs
,取绝对值。cmp
,compare的缩写,即比较函数。div
,除法。eq
,equal
的缩写,即相等比较。gt
,大于。gte
,小于等于,e表示equal
。lt
,小于。lte
,小于等于,e表示equal
。minus
,减法。mod
,取余。plus
,加法。pow
,次方。prec
,按精度舍入,参数表示整体位数。round
,按精度舍入,参数表示小数点后位数。sqrt
,开方。times
,乘法。toExponential
,转化为科学计数法,参数代表精度位数。toFied
,补全位数,参数代表小数点后位数。toJSON
和toString
,转化为字符串。toPrecision
,按指定有效位数展示,参数为有效位数。toNumber
,转化为JavaScript
中number
类型。valueOf
,包含负号(如果为负数或者-0)的字符串。
封装
import Big from 'big.js'
export const calcFn = {
add() {
const arg = Array.from(arguments)
return arg.reduce((total, num) => {
return new Big(total).plus(new Big(num))
}).toString() * 1
},
sub() {
const arg = Array.from(arguments)
return arg.reduce((total, num) => {
return new Big(total).minus(new Big(num))
}).toString() * 1
},
mul() {
const arg = Array.from(arguments)
return arg.reduce((total, num) => {
return new Big(total).times(new Big(num))
}).toString() * 1
},
divide() {
const arg = Array.from(arguments)
return arg.reduce((total, num) => {
return new Big(total).div(new Big(num))
}).toString() * 1
}
}
使用
calcFn.add(0.1, 0.2) !== 0.3 // false
bignumber.js
- 介绍:用于任意精度十进制和非十进制算术的 JavaScript 库。
- 特性:无依赖、包大小
8 KB
、兼容ECMAScript 3+
可以说适用于所有浏览器。 - 官网:GitHub
使用方法类似,同上。
decimal.js
- 介绍:为 JavaScript 提供十进制类型的任意精度数值。
- 特性:无依赖、包大小
12.6 KB
、兼容ECMAScript 3+
可以说适用于所有浏览器。 - 官网:GitHub
使用方法类似,同上。
Math.js
- 介绍:用 Javascript 编写的简单数学库,可能不维护了。
- 特性:是一个广泛的 JavaScript 和 Node.js 数学库。它具有灵活的表达式解析器,支持符号计算,带有大量内置函数和常量,并提供了一个集成的解决方案来处理不同的数据类型,如数字、大数、复数、分数、单位和矩阵。功能强大且易于使用。
- 官网:GitHub
总结
big.js
适用于大部分十进制算术应用程序,因为不接受NaN
或Infinity
作为合法值。而且不支持其他基数的值。如果项目中没有非十进制算术这非常适合用,而且关键是包足过小,哈哈自己造的轮子后面还是觉得库比较香哈。
bignumber.js
可能更适合金融应用,因为除非使用涉及除法的运算,否则用户无需担心会丢失精度。
decimal.js
可能更适合更科学的应用程序,因为它可以更有效地处理非常小的或大的值。例如,它没有bignumber.js
的限制,当将一个小指数的值与一个大指数的值相加时,bignumber.js
会尝试执行全精度运算,这可能会导致操作不可行。
如上所述,decimal.js
还支持非整数幂,并增加了三角函数和exp
,ln
和log
方法。这些添加使decimal.js
明显大于bignumber.js。