likes
comments
collection
share

探讨JavaScript小数点运算的精度问题及其解决方案

作者站长头像
站长
· 阅读数 38

参考资料:blog.csdn.net/weixin_3575…

运算精度丢失示例

探讨JavaScript小数点运算的精度问题及其解决方案

浮点数在运算过程会出现如上图的问题,浮点数运算精度丢失。

分析精度丢失的原因

这与Number类型在计算机上以双精度的存储方式有关系。

举个栗子:0.1(十进制)在计算机转成二进制后是一个无限循环小数(0.0001100110011...),所以计算出来与预期不符。

小数部分转换为二进制:

  1. 乘二取整法
    • 将十进制小数乘以2,然后取整数部分。
    • 记录下每一次乘以2后的整数部分。
    • 重复上述过程,每次都用上一步的积(除去已记录的整数部分)继续乘以2。
    • 直至乘积无限接近于0为止,或者达到你所需要的精度。
    • 将记录下来的整数部分从上到下(或从左到右)连缀起来,即得到小数部分的二进制表示。

整数部分转换为二进制:

  1. 除二取余法
    • 首先,将十进制整数除以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.jsdecimal.js
githubgithub.com/MikeMcl/big…github.com/MikeMcl/dec…
体积20KB+120KB+,有更佳丰富的方法可使用

实现思路

进行加法运算时,其内部实现的思路是基于定点数算法,以字符串或者其他非浮点数形式存储和处理大数,从而规避了JavaScript原生浮点数精度限制的问题。

步骤:

  1. 初始化大数:首先,big.js 会将传入的大数转换为内部表示格式,通常是将数字转换为字符串或其他可精确表示的形式。
  2. 对齐数位:由于加法需要对应数位相加,big.js 在进行加法之前会对参与运算的两个大数进行对齐,保证相同位置上的数字对应相加。
  3. 逐位相加:从最低位开始,逐位相加,同时记录进位(Carry)。如果有进位产生,下一位的相加结果需要加上这个进位。
  4. 循环加法及处理进位:继续对更高位进行同样的加法和进位处理,直到所有数位都已处理完毕。
  5. 处理最高位的进位:若在最后一步仍然有进位,则在最高位添加一个“1”。
  6. 结果规范化:将最终得到的内部表示转换回符合用户预期的输出格式,比如调整精度(根据配置的DP设置),并将结果作为一个新的 Big 对象返回。
转载自:https://juejin.cn/post/7351454390149447692
评论
请登录