探讨JavaScript小数点运算的精度问题及其解决方案
参考资料:blog.csdn.net/weixin_3575…
运算精度丢失示例
浮点数在运算过程会出现如上图的问题,浮点数运算精度丢失。
分析精度丢失的原因
这与Number类型在计算机上以双精度的存储方式有关系。
举个栗子:0.1(十进制)在计算机转成二进制后是一个无限循环小数(0.0001100110011...),所以计算出来与预期不符。
小数部分转换为二进制:
- 乘二取整法
- 将十进制小数乘以2,然后取整数部分。
- 记录下每一次乘以2后的整数部分。
- 重复上述过程,每次都用上一步的积(除去已记录的整数部分)继续乘以2。
- 直至乘积无限接近于0为止,或者达到你所需要的精度。
- 将记录下来的整数部分从上到下(或从左到右)连缀起来,即得到小数部分的二进制表示。
整数部分转换为二进制:
- 除二取余法
- 首先,将十进制整数除以2,得到商和余数。
- 将余数记下来,它是二进制数的最低位(最右边的位)。
- 然后用得到的商再次除以2,记录下新的余数。
- 重复以上步骤,每次都用商除以2并获取余数,直到商为0为止。
- 最后,将记录下来的余数从下到上(或从右到左)连缀起来,就得到了转换后的二进制数。
0.1(十进制)转成二进制的演算过程:
0.1 * 2 = 0.2,整数部分为0,写下0。
0.2 * 2 = 0.4,整数部分为0,写下0。
0.4 * 2 = 0.8,整数部分为0,写下0。
0.8 * 2 = 1.6,整数部分为1,写下1,并把小数部分0.6继续计算。
0.6 * 2 = 1.2,整数部分为1,写下1,并把小数部分0.2继续计算。
循环开始重复:0.2 * 2 = 0.4,整数部分为0,写下0...
这个过程会一直持续下去,形成一个无限循环:
0.1(十进制)≈ 0.0001100110011001...
JavaScript取了一个较为接近的值来存储。
解决方案
我们平台是金融交易方面的,经常会有浮点数的运算需求,计算基金或股票的总额、手续费。梳理一下项目中目前使用的数学计算库。
jt使用的数学计算库
使用的是远古时代的代码,从lct沿用下来,包含5种运算:加/减/乘/除/取模
代码量:math.js 114行
加法的代码实现
// 数字相加
export const add = (arg1: number | string, arg2: number | string): number => {
var r1, r2, m, c;
var s1 = arg1.toString(),
s2 = arg2.toString();
try {
r1 = s1.split(".")[1].length;
} catch (e) {
r1 = 0;
}
try {
r2 = s2.split(".")[1].length;
} catch (e) {
r2 = 0;
}
c = Math.abs(r1 - r2);
m = Math.pow(10, Math.max(r1, r2));
if (c > 0) {
var cm = Math.pow(10, c);
if (r1 > r2) {
arg1 = Number(s1.replace(".", ""));
arg2 = Number(s2.replace(".", "")) * cm;
} else {
arg1 = Number(s1.replace(".", "")) * cm;
arg2 = Number(s2.replace(".", ""));
}
} else {
arg1 = Number(s1.replace(".", ""));
arg2 = Number(s2.replace(".", ""));
}
return (arg1 + arg2) / m;
};
实现思路
支持number和string类型,把数字转成字符串放大,再转成整数,然后做加减乘除,结果再缩小回去。小数位数使用toFixed()解决。
金融数字计算主流的库
维度 | big.js | decimal.js |
---|---|---|
github | github.com/MikeMcl/big… | github.com/MikeMcl/dec… |
体积 | 20KB+ | 120KB+,有更佳丰富的方法可使用 |
实现思路
进行加法运算时,其内部实现的思路是基于定点数算法,以字符串或者其他非浮点数形式存储和处理大数,从而规避了JavaScript原生浮点数精度限制的问题。
步骤:
- 初始化大数:首先,
big.js
会将传入的大数转换为内部表示格式,通常是将数字转换为字符串或其他可精确表示的形式。 - 对齐数位:由于加法需要对应数位相加,
big.js
在进行加法之前会对参与运算的两个大数进行对齐,保证相同位置上的数字对应相加。 - 逐位相加:从最低位开始,逐位相加,同时记录进位(Carry)。如果有进位产生,下一位的相加结果需要加上这个进位。
- 循环加法及处理进位:继续对更高位进行同样的加法和进位处理,直到所有数位都已处理完毕。
- 处理最高位的进位:若在最后一步仍然有进位,则在最高位添加一个“1”。
- 结果规范化:将最终得到的内部表示转换回符合用户预期的输出格式,比如调整精度(根据配置的DP设置),并将结果作为一个新的
Big
对象返回。
转载自:https://juejin.cn/post/7351454390149447692