图文剖析 big.js 四则运算源码

这篇具有很好参考价值的文章主要介绍了图文剖析 big.js 四则运算源码。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

big.js,一个小型、快速的用于任意精度的十进制算术的JavaScript 库。

big.js 用于解决平常项目中进行算术运算时精度丢失引起的结果不准确的问题。和 big.js 类似的两个库 bignumber.js 和 decimal.js 也都是出自同一作者(MikeMcl)之手。

作者在 这里 详细说明了他们之间的区别

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

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

bignumber.js 可能更适合金融类应用,因为用户不用担心丢失精度,除非使用了涉及除法的操作。

这篇文章分别就 big.js 的解析函数,以及加减乘除运算的源码进行剖析,了解作者的设计思路。在四则运算的源码中,相比加减乘,除法运算最为复杂。

用法

创建 Big 对象时,new 操作符是可选的

x = new Big(123.4567)
y = Big('123456.7e-3')                 // 'new' is optional
z = new Big(x)
x.eq(y) && x.eq(z) && y.eq(z)          // true

构造函数

构造函数中关键代码如下

function Big(n) {
  var x = this;

  // 使用构造函数前面可以不带 new 关键字
  if (!(x instanceof Big)) return n === UNDEFINED ? _Big_() : new Big(n);

  // 如果传进来的参数已经是 Big 的实例对象,则复制一份,否则使用 parse 函数创建一个实例对象
  if (n instanceof Big) {
    x.s = n.s;
    x.e = n.e;
    x.c = n.c.slice();
  } else {
    if (typeof n !== 'string') {
      if (Big.strict === true && typeof n !== 'bigint') {
        throw TypeError(INVALID + 'value');
      }

      // 传入的如果是 -0 ,则转为字符串表示 '-0'
      n = n === 0 && 1 / n < 0 ? '-0' : String(n);
    }

    parse(x, n);
  }

使用构造函数前面可以不带 new 关键字

如果传进来的参数已经是 Big 的实例对象,则将实例对象的属性复制一份,否则使用 parse 函数为实例对象创建属性。

parse 函数

function parse(x, n) {
    var e, i, nl;

    // NUMERIC = /^-?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/i;
    if (!NUMERIC.test(n)) {
      throw Error(INVALID + 'number');
    }

    // Determine sign.
    x.s = n.charAt(0) == '-' ? (n = n.slice(1), -1) : 1;

    // Decimal point?
    if ((e = n.indexOf('.')) > -1) n = n.replace('.', '');

    // Exponential form?
    if ((i = n.search(/e/i)) > 0) {

      // Determine exponent.
      if (e < 0) e = i;
      e += +n.slice(i + 1);
      n = n.substring(0, i);
    } else if (e < 0) {

      // Integer.
      e = n.length;
    }

    nl = n.length;

    // Determine leading zeros.
    for (i = 0; i < nl && n.charAt(i) == '0';) ++i;

    if (i == nl) {

      // Zero.
      x.c = [x.e = 0];
    } else {

      // Determine trailing zeros.
      for (; nl > 0 && n.charAt(--nl) == '0';);
      x.e = e - i - 1;
      x.c = [];

      // Convert string to array of digits without leading/trailing zeros.
      for (e = 0; i <= nl;) x.c[e++] = +n.charAt(i++);
    }

    return x;
  }

parse 函数会为实例对象添加三个属性;

  • x.s,表示数字的符号,即是正数还是负数,即正负值,若是正数,x.s = 1,负数则为 -1
  • x.e,表示数字对应的指数表示法的指数,比如 n = 1234 的指数为 3
  • x.c,数字数组,比如 1234 转换后是 [1,2,3,4]
1234 会被转化为 

{
    c:[1,2,3,4],
    e:3,
    s:1
}

这种表示,和 IEEE 754 双精度浮点数的存储方式 很类似,而 JavaScript 的 Number类型就是一个双精度 64 位二进制格式 IEEE 754 值使用 64 位来表示 3 个部分:

  • 1 位用于表示符号(sign) (正数或者负数)
  • 11 位用于表示指数(exponent) (-1022 到 1023)
  • 52 位用于表示尾数(mantissa) (表示 0 和 1 之间的数值)

图文剖析 big.js 四则运算源码

下面分析 parse 函数转化的详细过程,以 Big('123400')Big('0.1234')Big('100e2') 为例

注意:Big('100e2') 中 100e2 以字符串形式传进来才能检测到 e ,Number形式的 Big(100e2),执行 parse 前会被转化为 Big(10000)

  1. 校验传入的值,只允许数字,'.1',指数形式的写法。比如 2.34.2 ,10e2
// NUMERIC = /^-?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/i;
if (!NUMERIC.test(n)) {
  throw Error(INVALID + 'number');
}

Big('123400'),Big('-0.1234'),Big('100e2') 都通过
  1. x.s = n.charAt(0) == '-' ? (n = n.slice(1), -1) : 1; 确定符号
Big('123400') => x.s = 1
Big('-0.1234') => x.s = -1   并且 -0.1234 => 0.1234
Big('100e2') => x.s = 1
  1. if ((e = n.indexOf('.')) > -1) n = n.replace('.', ''); 是否含有小数点,如果是,则删除小数点,并将 e 的初始值设为小数点的位置
Big('123400') => x.e = -1 , n = 123400
Big('-0.1234') => x.e = 1 , n = 01234
Big('100e2') => x.e = -1 , n = 100e2
  1. 如果数字是科学表示法,比如 100e2 ,e 的位置是 3,e 后面的指数是 2 ,则 x.e = 3 + 2
if ((i = n.search(/e/i)) > 0) {

  // Determine exponent.
  if (e < 0) e = i;
  e += +n.slice(i + 1);
  n = n.substring(0, i);
} else if (e < 0) {

  // Integer.
  e = n.length;
}


Big('123400') 
x.e = -1  =>  x.e = n.length = 6
n = 123400

Big('-0.1234')
x.e = 1 
n = 01234

Big('100e2')
x.e = -1  =>  x.e = e 在 100e2 中的位置 + e 后面紧跟的指数系数 = 3 + 2 = 5
n = 100e2  =>  n = 100
  1. nl = n.length; nl 表示传进来的数字的长度
Big('123400') 
x.e = 6
n = 123400
nl = 6

Big('-0.1234')
x.e = 1 
n = 01234
nl = 5

Big('100e2')
x.e = 5
n = 100
nl = 3
  1. for (i = 0; i < nl && n.charAt(i) == '0';) ++i; 确定数字是否有前置 0 ,这里的 i 表示第一个不为 0 的数字的位置,也可以表示数字前面有多少个 0
Big('123400') 
x.e = 6
n = 123400
nl = 6
i = 0

Big('-0.1234')
x.e = 1 
n = 01234
nl = 5
i = 1

Big('100e2')
x.e = 5
n = 100
nl = 3
i = 0
  1. 如果 i = nl,则说明传进来的输入是一个 0 或者多个 0
if (i == nl) {

  // Zero.
  x.c = [x.e = 0];
} else {

  // 排除尾随 0,nl 为最后一个不为 0 的数字的位置
  for (; nl > 0 && n.charAt(--nl) == '0';);
  x.e = e - i - 1;
  x.c = [];

  // 传进来的数字,排除掉前置 0 和尾随 0 后,转换为数字数组
  for (e = 0; i <= nl;) x.c[e++] = +n.charAt(i++);
}


Big('123400') 
//因为默认 e 是 n.length,而Big指数表示是 1.234 * 10^5,所以这里 x.e 要减一
x.e = 6  =>  x.e = e - i - 1 = 6 - 0 - 1 = 5  
n = 123400
nl = 6  =>  排除尾随 0,nl 为最后一个不为 0 的数字的位置  =>  nl = 3
i = 0
x.c => 选取 n 中从 i 到 nl 的数字组成数组 [1,2,3,4]

Big('-0.1234')
x.e = 1  =>  x.e = e - i - 1 = 1 - 1 - 1 = -1 
n = 01234
nl = 5  =>  排除尾随 0,nl 为最后一个不为 0 的数字的位置  =>  nl = 4
i = 1
x.c => 选取 n 中从 i 到 nl 的数字组成数组 [1,2,3,4]

Big('100e2')
x.e = 5  =>  x.e = e - i - 1 = 5 - 0 - 1 = 4  
n = 100
nl = 3  =>  排除尾随 0,nl 为最后一个不为 0 的数字的位置  =>  nl = 0
i = 0
x.c => 选取 n 中从 i 到 nl 的数字组成数组 [1]

最后 Big('123400'),Big('-0.1234'),Big('100e2') 将转换为

Big('123400') 
x.s = 1
x.e = 5
x.c = [1,2,3,4]

Big('-0.1234') 
x.s = -1
x.e = -1
x.c = [1,2,3,4]

Big('100e2') 
x.s = 1
x.e = 4
x.c = [1]

至此 parse 函数逻辑结束,接下来分别剖析下加减乘除运算;

加法

源码

P.plus = P.add = function (y) {
    var e, k, t,
      x = this,
      Big = x.constructor;

    y = new Big(y);

    // 校验符号是否不同
    if (x.s != y.s) {
      y.s = -y.s;
      return x.minus(y);
    }

    var xe = x.e,
      xc = x.c,
      ye = y.e,
      yc = y.c;

    // 校验是否是 0
    if (!xc[0] || !yc[0]) {
      if (!yc[0]) {
        if (xc[0]) {
          y = new Big(x);
        } else {
          y.s = x.s;
        }
      }
      return y;
    }

    xc = xc.slice();

    // 前面加上零使指数均衡
    // Note: reverse faster than unshifts.
    if (e = xe - ye) {
      if (e > 0) {
        ye = xe;
        t = yc;
      } else {
        e = -e;
        t = xc;
      }

      t.reverse();
      for (; e--;) t.push(0);
      t.reverse();
    }

    // 让 xc 存放长度更长的数字
    if (xc.length - yc.length < 0) {
      t = yc;
      yc = xc;
      xc = t;
    }

    e = yc.length;

    for (k = 0; e; xc[e] %= 10) k = (xc[--e] = xc[e] + yc[e] + k) / 10 | 0;

    // No need to check for zero, as +x + +y != 0 && -x + -y != 0

    if (k) {
      xc.unshift(k);
      ++ye;
    }

    // 删除尾随 0
    for (e = xc.length; xc[--e] === 0;) xc.pop();

    y.c = xc;
    y.e = ye;

    return y;
  };
  1. 如果符号不同,则转为减法运算;比如 -x + y 就是 y - x ,x + -y 就是 x - y
if (x.s != y.s) {
  y.s = -y.s;
  return x.minus(y);
}
  1. 其中两个数字是不是 0,其中有一个为 0,则直接返回另外一个
if (!xc[0] || !yc[0]) {
  if (!yc[0]) {
    if (xc[0]) {
      y = new Big(x);
    } else {
      y.s = x.s;
    }
  }
  return y;
}
  1. 比较指数幂差,较小的一方,在前面补零,方便后续加法操作;并且将指数幂较大的一方,作为两数相加的结果的指数幂的初始值。
if (e = xe - ye) {
  if (e > 0) {
    ye = xe; // 将指数幂较大的一方,作为两数相加的结果的指数幂的初始值
    t = yc;
  } else {
    e = -e;
    t = xc;
  }

  t.reverse();
  for (; e--;) t.push(0);
  t.reverse();
}

比如 1234 + 12 
1234 在实例对象上是以数字数组形式表示 [1,2,3,4]
12 则是 [1,2]
为方便后续数组按照位置进行加法运算,这里需要给 12 补零
[1,2,3,4]
    +
[0,0,1,2]
  1. xc 存放长度更长的数字
if (xc.length - yc.length < 0) {
  t = yc;
  yc = xc;
  xc = t;
}
  1. 接下来是加法逻辑
e = yc.length;

for (k = 0; e; xc[e] %= 10) k = (xc[--e] = xc[e] + yc[e] + k) / 10 | 0;

if (k) {
  xc.unshift(k);
  ++ye;
}

// 删除尾随 0
for (e = xc.length; xc[--e] === 0;) xc.pop();

k 保存进位的值

  • 初始化进位值为 0 ,e 为 yc 长度,执行下面循环体
  • (xc[--e] = xc[e] + yc[e] + k) 计算 xc[e] 加上 yc[e] 加上上一次计算结果进位的值;
  • 随后 xc[--e] 保存计算后的进位的数值,e--
  • 最后 xc[e] 保存计算后的个位数值

上面过程用图例表示如下

图文剖析 big.js 四则运算源码

减法

源码

P.minus = P.sub = function (y) {
    var i, j, t, xlty,
      x = this,
      Big = x.constructor,
      a = x.s,
      b = (y = new Big(y)).s;

    // 确定符号,x - (-y) = x + y    - x - y = -x + (-y)
    if (a != b) {
      y.s = -b;
      return x.plus(y);
    }

    var xc = x.c.slice(),
      xe = x.e,
      yc = y.c,
      ye = y.e;

    // 判断是否为 0
    if (!xc[0] || !yc[0]) {
      if (yc[0]) {
        y.s = -b;
      } else if (xc[0]) {
        y = new Big(x);
      } else {
        y.s = 1;
      }
      return y;
    }

    // 比较两数指数幂大小,给指数幂小的一方补零,方便后续相减;
    // 比如 1234 - 23  parse函数解析后 => [1,2,3,4] - [2,3]  为了使 [2,3] 对应十位,个位
    // 在前面补 0 ,即 [1,2,3,4] - [0,0,2,3]
    // 再比如 66 - 233  parse函数解析后 => [6,7] - [2,3,3],同样为了使 6,7对应十位,个位
    // 在前面补 0 ,即 [0,6,7] - [2,3,3]
    if (a = xe - ye) {

      if (xlty = a < 0) {
        a = -a;
        t = xc;
      } else {
        ye = xe;
        t = yc;
      }

      t.reverse();
      for (b = a; b--;) t.push(0);  // 补零
      t.reverse();
    } else {

      // 若指数幂相等,不需要补零,则比较两数大小,从最大位开始比较;
      // 比如 [2,3,4] 和 [1,2,3] 最大位是百位,若百位的数字不相等,则可得出孰大孰小
      j = ((xlty = xc.length < yc.length) ? xc : yc).length;

      for (a = b = 0; b < j; b++) {
        if (xc[b] != yc[b]) {
          xlty = xc[b] < yc[b];
          break;
        }
      }
    }
    
    // 对于被减数 x 和减数 y
    // 如果 x - y < 0,则交换两数,并改变符号;比如 2 - 4 = -(4-2)
    if (xlty) {
      t = xc;
      xc = yc;
      yc = t;
      y.s = -y.s;
    }

    // 如果被减数的数字数组长度小于减数,则给被减数的末尾添加 0 
    // 比如 12 - 0.0009  parse函数解析后 => [1,2] - [0,0,0,0,9]
    // 因为 9 是小数后几位,相应的需要给 [1,2]末尾补 0 ,即 [1,2,0,0,0] - [0,0,0,0,9]
    if ((b = (j = yc.length) - (i = xc.length)) > 0) for (; b--;) xc[i++] = 0;

    // 从 xc 中减去 yc
    for (b = i; j > a;) {
      if (xc[--j] < yc[j]) {
        for (i = j; i && !xc[--i];) xc[i] = 9;
        --xc[i];
        xc[j] += 10;
      }

      xc[j] -= yc[j];
    }

    // 去掉运算结果末尾 0 
    for (; xc[--b] === 0;) xc.pop();

    // 去掉运算结果前置 0 ,并减去相应指数幂
    for (; xc[0] === 0;) {
      xc.shift();
      --ye;
    }

    // 运算结果为 0 的情况
    if (!xc[0]) {

      // n - n = +0
      y.s = 1;

      xc = [ye = 0];
    }

    y.c = xc;
    y.e = ye;

    return y;
  };

减法前面的逻辑和加法类似,这里不再赘述,已在上面代码注释中说明,下面是减法的核心逻辑

// 从 被减数 xc 中减去减数 yc
// a 是 xc 和 yc 的幂的差值,j 是 yc 的长度,这里循环条件用 j > a,表示循环 j-a 次
// 比如 120 - 9  =>  [1,2,0]-[0,0,9] 指数幂差是 2 ,减数数字数组长度是 3 ,则只需要循环 3-2=1 次
// 比如 120 - 0.009 => [1,2,0,0,0,0]-[0,0,0,0,0,9] 指数幂差是 5 ,减数数字数组长度是 6 ,则只需要循环 6-5=1 次
for (b = i; j > a;) { 
  if (xc[--j] < yc[j]) {
  //从后往前遍历xc,当碰到值为0 ,将值改为 9;
  //比如 [1,0,0]-[0,0,9] => [0,9,10] -
    for (i = j; i && !xc[--i];) xc[i] = 9; 
    --xc[i];
    xc[j] += 10;
  }

  xc[j] -= yc[j];
}

上面过程用图例表示如下,xc 表示被减数,yc 表示减数

1、若 xc 末尾项大于等于 yc 末尾项,比如 [1,2,3]和[0,0,2],则直接相减。

图文剖析 big.js 四则运算源码

2、若 xc 末尾项小于 yc 末尾项,则执行以下逻辑

for (i = j; i && !xc[--i];) xc[i] = 9;

上面代码表示从 当前进行相减运算的元素的位置(j) 往前遍历被减数 xc 每个元素,当元素值为 0 时,将值改为 9,直至上一个元素值不为 0 ,循环结束。
图文剖析 big.js 四则运算源码

至此,减法逻辑结束。

乘法

源码

P.times = P.mul = function (y) {
    var c,
      x = this,
      Big = x.constructor,
      xc = x.c,
      yc = (y = new Big(y)).c,
      a = xc.length,
      b = yc.length,
      i = x.e,
      j = y.e;

    // 确定结果的符号
    y.s = x.s == y.s ? 1 : -1;

    // 其中一个为 0 ,返回结果为 0
    if (!xc[0] || !yc[0]) {
      y.c = [y.e = 0];
      return y;
    }

    // 初始化结果的指数
    y.e = i + j;

    // 对比 xc,yc 长度,xc 存放长度更长的一方
    if (a < b) {
      c = xc;
      xc = yc;
      yc = c;
      j = a;
      a = b;
      b = j;
    }

    // 用 0 初始化结果数组
    for (c = new Array(j = a + b); j--;) c[j] = 0;

    // i is initially xc.length.
    for (i = b; i--;) {
      b = 0;

      // a is yc.length.
      for (j = a + i; j > i;) {

        // Current sum of products at this digit position, plus carry.
        b = c[j] + yc[i] * xc[j - i - 1] + b;
        c[j--] = b % 10;

        // carry
        b = b / 10 | 0;
      }

      c[j] = b;
    }

    // 如果有最终进位,则增加结果的指数,否则删除头部的 0
    if (b) ++y.e;
    else c.shift();

    // 删除尾部的 0
    for (i = c.length; !c[--i];) c.pop();
    y.c = c;

    return y;
  };

乘法源码的主要逻辑是下面这一段

for (i = b; i--;) {
  b = 0;

  for (j = a + i; j > i;) {

    // 当前数字位置的总和,加上进位
    b = c[j] + yc[i] * xc[j - i - 1] + b;
    c[j--] = b % 10;

    // 进位值
    b = b / 10 | 0;
  }

  c[j] = b;
}

描述的其实就是以前老师教我们在纸上乘法运算的过程:

图文剖析 big.js 四则运算源码

123*12 来举例子分析上面这段代码

  • xc 是乘数 [1,2,3],yc 是被乘数 [1,2],b 是 yc 长度,a 是 xc 长度

  • c 是保存结果的数组,定义的长度是 a+b

两个数相乘得到的结果长度可能是 a+b,也有可能是 a+b-1。所以后面需要删除数组头部的 0

  1. for (i = b; i--;) 首先是外层循环,从数组长度较短的被乘数开始循环,将 b 赋值给 i,i 充当 yc 的长度,而 b 用来保存进位的值
  2. b = 0 定义进位的值
  3. for (j = a + i; j > i;) 内层乘数(123)的循环,这里的 j 表示在结果数组 c 中的位置

for (j = a + i; j > i;) 实际上就是 for ( j = 乘数长度 + 当前被乘数数字的位置 ),这里是因为当第二轮外层循环时,123 * 1 的时候,1 是 12 的 十位,所以在 j 也应该从十位开始保存计算结果。

第一轮外层循环

图文剖析 big.js 四则运算源码

第二轮外层循环

图文剖析 big.js 四则运算源码

  1. b = c[j] + yc[i] * xc[j - i - 1] + b 当前数字位置的总和,加上进位

图文剖析 big.js 四则运算源码

  1. c[j--] = b % 10; 当前位置去整取余
  2. b = b / 10 | 0; 进位值取整

至此乘法运算逻辑结束

除法

源码

P.div = function (y) {
    var x = this,
      Big = x.constructor,
      a = x.c,                  // dividend
      b = (y = new Big(y)).c,   // divisor
      k = x.s == y.s ? 1 : -1,
      dp = Big.DP;

    if (dp !== ~~dp || dp < 0 || dp > MAX_DP) {
      throw Error(INVALID_DP);
    }

    // Divisor is zero?
    if (!b[0]) {
      throw Error(DIV_BY_ZERO);
    }

    // Dividend is 0? Return +-0.
    if (!a[0]) {
      y.s = k;
      y.c = [y.e = 0];
      return y;
    }

    var bl, bt, n, cmp, ri,
      bz = b.slice(),
      ai = bl = b.length,
      al = a.length,
      r = a.slice(0, bl),   // remainder
      rl = r.length,
      q = y,                // quotient
      qc = q.c = [],
      qi = 0,
      p = dp + (q.e = x.e - y.e) + 1;    // precision of the result

    q.s = k;
    k = p < 0 ? 0 : p;

    // Create version of divisor with leading zero.
    bz.unshift(0);

    // Add zeros to make remainder as long as divisor.
    for (; rl++ < bl;) r.push(0);

    do {

      // n is how many times the divisor goes into current remainder.
      for (n = 0; n < 10; n++) {

        // Compare divisor and remainder.
        if (bl != (rl = r.length)) {
          cmp = bl > rl ? 1 : -1;
        } else {
          for (ri = -1, cmp = 0; ++ri < bl;) {
            if (b[ri] != r[ri]) {
              cmp = b[ri] > r[ri] ? 1 : -1;
              break;
            }
          }
        }

        // If divisor < remainder, subtract divisor from remainder.
        if (cmp < 0) {

          // Remainder can't be more than 1 digit longer than divisor.
          // Equalise lengths using divisor with extra leading zero?
          for (bt = rl == bl ? b : bz; rl;) {
            if (r[--rl] < bt[rl]) {
              ri = rl;
              for (; ri && !r[--ri];) r[ri] = 9;
              --r[ri];
              r[rl] += 10;
            }
            r[rl] -= bt[rl];
          }

          for (; !r[0];) r.shift();
        } else {
          break;
        }
      }

      // Add the digit n to the result array.
      qc[qi++] = cmp ? n : ++n;

      // Update the remainder.
      if (r[0] && cmp) r[rl] = a[ai] || 0;
      else r = [a[ai]];

    } while ((ai++ < al || r[0] !== UNDEFINED) && k--);

    // Leading zero? Do not remove if result is simply zero (qi == 1).
    if (!qc[0] && qi != 1) {

      // There can't be more than one zero.
      qc.shift();
      q.e--;
      p--;
    }

    // Round?
    if (qi > p) round(q, p, Big.RM, r[0] !== UNDEFINED);

    return q;
  };

在除法运算中,对于 a/b , a 是被除数,b 是除数,下面依次分析上面代码

  1. if (dp !== ~~dp || dp < 0 || dp > MAX_DP) 判断 dp 是不是大于 0 的整数,并且小于 MAX_DP,这里的 dp 可以自己设置
Big.DP = 30
  1. 除数为 0 则抛出错误。
if (!b[0]) {
  throw Error(DIV_BY_ZERO);
}
  1. 被除数是 0 则返回值为 0 的实例对象
if (!a[0]) {
  y.s = k;
  y.c = [y.e = 0];
  return y;
}
  1. 接下来是除法运算逻辑,定义变量的那一段不贴了,直接看 do while 循环
do {

  // n 是循环次数,表示从当前位置的余数中可以分出多少个除数来,也就是当前位置的商。
  for (n = 0; n < 10; n++) {

    // 比较除数和余数大小
    if (bl != (rl = r.length)) {
      cmp = bl > rl ? 1 : -1;
    } else {
      for (ri = -1, cmp = 0; ++ri < bl;) {
        if (b[ri] != r[ri]) {
          cmp = b[ri] > r[ri] ? 1 : -1;
          break;
        }
      }
    }

    // 除数小于余数,则继续从余数中减去除数
    if (cmp < 0) {

      // Remainder can't be more than 1 digit longer than divisor.
      // Equalise lengths using divisor with extra leading zero?
      for (bt = rl == bl ? b : bz; rl;) {
        if (r[--rl] < bt[rl]) {
          ri = rl;
          for (; ri && !r[--ri];) r[ri] = 9;
          --r[ri];
          r[rl] += 10;
        }
        r[rl] -= bt[rl];
      }

      for (; !r[0];) r.shift();
    } else {
      break;
    }
  }

  // qc 数组保存商
  qc[qi++] = cmp ? n : ++n;

  // 更新余数
  if (r[0] && cmp) r[rl] = a[ai] || 0;
  else r = [a[ai]];

} while ((ai++ < al || r[0] !== UNDEFINED) && k--);

这个循环做了这些事情:以 1234 / 9 为例;

图文剖析 big.js 四则运算源码

  • 将当前位置的余数 1 和除数 9 比较大小,(一开始余数取的是被除数的前面 n 位,n 和除数的长度大小相同,所以取的是 1 )。先比较长度,长度相同再比较大小。
  • 若除数大于当前位置余数,则跳出循环(当前循环次数即为当前位置的商,9 > 1 ,那么当前循环次数为 0 ,即当前位置商为 0 )
  • 然后保存商 qc[qi++] = cmp ? n : ++n;
  • 最后更新当前余数,除数大于余数时,则当前余数向后借一位,余数就由 1 变为了 12
if (r[0] && cmp) r[rl] = a[ai] || 0;
  else r = [a[ai]];
  • 若除数小于当前余数,则继续从余数中减去除数 (开始减法 for 循环)

图文剖析 big.js 四则运算源码

  • 然后再次保存商 qc[qi++] = cmp ? n : ++n;
  • 然后再次更新当前余数,除数大于余数时,则当前余数向后借一位,余数就由 3 变为了 33
  • 当商数组的长度没有达到指定的精度总和,继续上面的步骤,直至循环结束;

指定的精度总和指的是 Big.DP(默认20) + (被除数的指数-除数的指数); 1234 / 9 的指定精度总和是 23。

  1. 商数组长度大于 1 的情况下,删除数组前面的 0 ;如果是 商就是 0 ,比如 0/1 = 0,这种情况不必删除 0 了
 if (!qc[0] && qi != 1) {
  qc.shift();
  q.e--;
  p--;
}
  1. 舍入操作 if (qi > p) round(q, p, Big.RM, r[0] !== UNDEFINED);

至此除法逻辑结束

注意事项

big.js 用数组存储值,类似 高精度计算,只不过 big.js 是数组中每个位置存储一个值,然后对每个位置进行运算;而对超级大的数字(数百或数千位数值时),big.js 算术运算不如 bignumber.js 快。

例如,bignumber.js 将数字1234.56789的数字存储为[1234,56789000000000] ,即以两个1e14为基数的数组形式存储,而 big.js 存储的数字与[1,2,3,4.5,6,7,8,9]相同,即以9个10为基数的数组形式存储。前者的算术运算可能更快,因为需要处理的元素较少。在实践中,这可能只有在使用数百或数千位数值时才会有所不同。

在使用 big.js 进行运算时需要注意有时候没有设置足够大的精度,会导致结果不是想要的。

Big.DP = 20
+Big(1).div('11111111').times('11111111') // 0.9999999999999999
// 0.9999999999999999 在 Number 编码的可以表示的准确精度范围内

Big.DP = 30
+Big(1).div('11111111').times('11111111') // 1
// 而设置 Big.DP = 30 后
//结果数组保存的是 999999999999999999999999
//超过了 Number 编码的可以表示的准确精度范围,则会舍入为 1

总结

本文剖析了 big.js 解析函数源码,四则运算源码,分别用图文详细描述了运算过程,一步步还原了作者的构思。有不正确的地方或者不同见解还请各位大佬提出来。文章来源地址https://www.toymoban.com/news/detail-747456.html

到了这里,关于图文剖析 big.js 四则运算源码的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Python 四则运算的几种方法?

    Python的四则运算主要有以下几种方法: 1、使用基本算术运算符: Python支持基本的算术运算符,包括加(+), 减(-), 乘(*), 除(/) 和求模运算符(%), 可以用于数值类型的数据,例如整数(int)、浮点数(float)等。例如: 2、使用math模块中的函数: Python的标准库中提供了一个math模块,其

    2024年03月21日
    浏览(40)
  • C语言入门 Day_5 四则运算

    目录 前言 1.四则运算 2.其他运算 3.易错点 4.思维导图 图为世界上第一台通用计算机 ENIAC , 于1946年2月14日在美国宾夕法尼亚大学诞生。发明人是美国人 莫克利(JohnW.Mauchly )和 艾克特(J.PresperEckert) 。 计算机的最开始的功能就是用来计算的,今天我们就开始学习计算机的计

    2024年02月12日
    浏览(42)
  • Python四则运算“计算器”——整数口算练习“小程序”

    设定练习题目数量、最大取值整数数,即时判定,答对鼓励答错打印正确结果。 【学习的细节是欢悦的历程】 Python 官网 :https://www.python.org/ Free :大咖免费“ 圣经 ”教程《 python 完全自学教程 》,不仅仅是基础那么简单…… 地址:https://lqpybook.readthedocs.io/   自学并不是

    2024年02月07日
    浏览(83)
  • C语言数据结构篇——用栈实现四则运算

    作者名:Demo不是emo  主页面链接: 主页传送门 创作初心: 舞台再大,你不上台,永远是观众,没人会关心你努不努力,摔的痛不痛,他们只会看你最后站在什么位置,然后羡慕或鄙夷 座右铭: 不要让时代的悲哀成为你的悲哀 专研方向: 网络安全,数据结构 每日emo: 我爱

    2024年02月09日
    浏览(54)
  • 4.利用matlab符号矩阵的四则运算(matlab程序)

    1. 简述     符号对象的建立 sym函数 sym函数用于建立单个符号对象,其常用调用格式为: 符号对象名=sym(A) 1 将由A来建立符号对象,其中,A可以是一个数值常量、数值矩阵或数值表达式(不加单引号),此时符号对象为一个符号常量;A也可以是一个变量名(加单引号),这时符号对

    2024年02月13日
    浏览(46)
  • 华为OD机试真题(Java),四则运算(100%通过+复盘思路)

    华为OD机试 2023B卷题库疯狂收录中,刷题 点这里 输入一个表达式(用字符串表示),求这个表达式的值。 保证字符串中的有效字符包括 [‘0’-‘9’],‘+’,‘-’, ‘*’,‘/’ ,‘(’, ‘)’,‘[’, ‘]’,‘{’ ,‘}’ 。且表达式一定合法。 数据范围:表达式计算结果和过程中

    2024年02月07日
    浏览(49)
  • Jmeter之BeanShell取出参数进行四则运算,并判断是否正确

    首先调用余额接口,使用正则提取响应中的余额字段,记作变量acctBal1 做支付交易 再次调用余额接口,使用正则提取响应中的余额字段,记作变量acctBal2 最后在结果树中可以看到断言错误的信息,断言正确时没有提示 以下是beanshell断言脚本

    2024年02月13日
    浏览(45)
  • 学校头歌作业1_2四则运算(头歌作业[Python])

    在CSDN上补充前几期的内容 第1关:整数四则运算 第2关:浮点类型四则运算 第3关:用户输入数值和符号 第4关:随机出题的四则运算

    2024年02月08日
    浏览(62)
  • STM32简单的四则运算计算器,不需要操作系统

    能实现简单的四则运算,加减乘除,不需要操作系统,五个按键加上一个LCD屏幕就可以解决。 1.按键初始化  因为设置的是上拉输入,所以是接地。 2.按键处理函数 3.主函数 KEY2按键。按下去之后标志位flag就会发生变化,当确定flag变成1的时候在按下KEY0和KEY1时,x,y的值就是

    2024年02月11日
    浏览(38)
  • 【数据结构】【栈(stack)应用】四则运算表达式求值(带括号)

            先理解原理,再看代码,注意标红字体很重要!结尾附完整测试代码,C语言实现! 更新留言:         本来是侧重演示栈结构的使用,后面评论区发现很多朋友对这个四则运算比较感兴趣,特此做了更新,新增了对负数的运算支持。         若您也有新的想法

    2024年02月08日
    浏览(43)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包