借助BigInt在JavaScript实现高精度小数运算

开篇

BigInt是JavaScript的类型之一,用于表示超过 2 53 − 1 2^{53} - 1 2531的数,但是只支持小数,而有时常常需要使用到小数运算,这该如何实现呢?

正文

BigInt仅支持整数,但我们可以获取字符串形式的高精度小数(以下统称BigNumber)的小数点位置,然后转换为BigInt,再在计算结束后重新添加小数点以实现BigNumber的运算。

所有前置函数

function tenTimes (val) { //10的val次方
  return 10n ** BigInt(val);
};
function times (val, num) { //重复字符串val num次
  return val.repeat(num);
};
function toNum (val) { //把val转换为数字并把NaN转为0
  return isNaN(Number(val)) ? 0 : Number(val);
};
function removeLeft (str, pattern) { //删除字符串str左侧的所有指定字符串pattern
  const regex = new RegExp("^" + pattern + "+");
  return str.replace(regex, "");
};
function removeRight (str, pattern) { //删除字符串str右侧的所有指定字符串pattern
  const regex = new RegExp(pattern + "+$");
  return str.replace(regex, "");
};

转换

num='123.456'
/*
index: 0 1 2 3 4 5 6
value: 1 2 3 . 4 5 6
*/
len=num.indexOf('.') //获取高精度小数的小数点位置
//3
int=num.slice(0,len) //正数部分
//'123'
dec=num.slice(len+1) //小数部分
//'456'
int+dec
//'123456'
num.length-len-1 //小数部分长度
// 3

整理上述代码,可以得到获取BigNumber的BigInt表示形式与小数部分长度。

function toBigNumber (num) {
  let str = String(num),
    P = str.indexOf("."); //获取小数点位置
  P = P === -1 ? str.length - 1 : P; //处理无小数点情况
  let noDecNumber=str.replace(".", ""); //删除小数点
  let try_ = Number(noDecNumber);
    let [str_, len_] = isNaN(try_)
      ? ["0", 0]
      : noDecNumber.indexOf(".") === -1
        ? [noDecNumber, str.length - P - 1]
        : ["0", 0]; //处理try_不是合法整数的情况
   return {
     num: BigInt(str_),
     len: len_,
  };
};

这样转换我们便得到了BigNumber。

统一位数

计算时,两个BigNumber的小数位数需要统一。

/*
num1:
  index: 0 1 2 3 4 5 6
  value: 1 2 3 . 4 5 6
num2:
  index: 0 1 2 3 4 5 6
  value: 1 2 3 4 . 5 6
          ↓↓
num1:
  index: 0 1 2 3 4 5 6 7 \ 0 1 2 3 4 5 6
  value: 0 1 2 3 . 4 5 6 → 0 1 2 3 4 5 6
num2:
  index: 0 1 2 3 4 5 6 7 \ 0 1 2 3 4 5 6
  value: 1 2 3 4 . 5 6 0 → 1 2 3 4 5 6 0
*/

可以发现,统一位数便是将小数部分长度较小的BigNumber(minBN)左移 a b s ( n u m 1. l e n − n u m 2. l e n ) abs(num1.len-num2.len) abs(num1.lennum2.len) Δ l e n \Delta len Δlen )位,便是minBN转换为 m i n B N × ( 1 0 Δ l e n ) minBN \times (10^{\Delta len}) minBN×(10Δlen)

整理代码,得出:

function toSamePer (num, num2) {
  let a = toBigNumber(num),
    b = toBigNumber(num2);
  let len = Math.max(a.len, b.len);
  if (len === a.len)
    b.num = b.num * tenTimes(len - b.len);
  else
    a.num = a.num * tenTimes(len - a.len);
  a.len = b.len = len;
  return {
    num: a,
    num2: b,
  };
};

我们得到了统一两个BigNumber的小数位数的函数。

格式化

计算好后,我们还需要将BigNumber转回字符串形式的BigNumber,如:

num='-123456'
//num是计算后的BigNumber BigInt表现形式
len=3
//len是小数部分长度

//先判断是否是负数
flag=0
if (num.startsWith("-")) {
  num = num.replace("-", "");
  flag = 1;
}
//flag=1,num='123456'
//再在开头补0,测试时发现不补0会出现位数不够的情况
num = times("0", len * 2) + num
//000000123456
int = num.slice(0, num.length - len)
//000000123
dec = num.slice(num.length - len)
//         456
int = removeLeft(int, '0')
dec = removeRight(dec, '0')
//删除整数部分左侧的0与小数部分右侧的0
int = int === "" ? "0" : int
dec = dec === "" ? "" : "." + dec
//整理处理特殊情况
result = int + dec
//合并
result = (flag ? "-" : "") + result
//如果是负数加上负号

整理代码:

function formatNum (result, len) {
  result = String(result);
  let flag = 0;
  if (result.startsWith("-")) {
    result = result.replace("-", "");
    flag = 1;
  }
  result = times("0", len * 2) + result;
  let int = result.slice(0, result.length - len),
    dec = result.slice(result.length - len);
  int = removeLeft(int, '0');
  dec = removeRight(dec, '0');
  int = int === "" ? "0" : int;
  dec = dec === "" ? "" : "." + dec;
  let result_ = int + dec;
  result_ = (flag ? "-" : "") + result_;
  return result_;
};

这样,格式化BigNumber为字符串形式的函数就完成了。

计算

计算就很简单了。

function addFunc(num, num2) {
  let { num: a, num2: b } = toSamePer(num, num2);
  let result = a.num + b.num;
  return formatNum(result, a.len);
};
function subFunc (num, num2) {
  let { num: a, num2: b } = toSamePer(num, num2);
  let result = a.num - b.num;
  return formatNum(result, a.len);
};
function mulFunc (num, num2) {
  let { num: a, num2: b } = toSamePer(num, num2);
  let result = a.num * b.num;
  return formatNum(result, a.len * 2);
  //乘法是两个len,因为是两个小数部分长度为len的BigNumber相乘得出的
};
function divFunc (num, num2) {
  let { num: a, num2: b } = toSamePer(num, num2);
  if (b.num === 0n) return Infinity; //除数为0则Infinity
  let result = (a.num * tenTimes(100)) / b.num;
  return formatNum(result, 100);
};
function powFunc (num, num2) {
  let a = toBigNumber(num),
    b = Math.trunc(toNum(num2)), //处理指数非整数
    flag = 0;
  if (a.num === 0n) return '0'; //a^b,a为0则结果为0
  else if (b.num === 0n) return '1'; //a非0时b为0则结果为1
  if (b < 0) (flag = 1), (b = -b); //处理负指数
  let result = a.num ** BigInt(b);
  if (!flag) return formatNum(result, a.len * b);
  else return divFunc("1", formatNum(result, a.len * b)); //处理负指数
};
function modFunc (num, num2) {
  let { num: a, num2: b } = toSamePer(num, num2);
  if (b.num === 0n) return '0'; //a%b,b为0则结果为0
  let result = a.num % b.num;
  return formatNum(result, a.len);
};

结语

差不多就这些了,新人第一次写文,如有疏漏请多包涵()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值