big.js 方法实现源码

在我们常见的JavaScript数字运算中,小数和大数都是会让我们比较头疼的两个数据类型。

在大数运算中,由于number类型的数字长度限制,我们经常会遇到超出范围的情况。比如在我们传递Long型数据的情况下,我们就只能把它转换到字符串进行传递和处理。
而在小数点数字进行运算的过程中,JavaScript又由于它的数据表示方式,从而导致了小数运算会有不准确的情况。最经典的一个例子就是0.3-0.2,并不等于0.1,而是等于0.09999999999999998。
首先,让我们来看下,big.js这个库到底是如何使用的,具体有哪些应用的场景和功能。

 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)
 ​
 ​
 0.3 - 0.1                              // 0.19999999999999998
 x = new Big(0.3)
 x.minus(0.1)                           // "0.2"
 x                                      // "0.3"

源码:

 function Big(n) {
  var x = this;
 ​
  // 支持函数调用方式进行初始化,可以不使用new操作符
  if (!(x instanceof Big)) return n === UNDEFINED ? _Big_() : new Big(n);
 ​
  // 原型链判断,确认传入值是否已经为Big类的实例
  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) {
  throw TypeError(INVALID + 'number');
  }
 ​
  // 确定是否为-0,如果不是,转化为字符串.
  n = n === 0 && 1 / n < 0 ? '-0' : String(n);
  }
 ​
  // parse函数只接受字符串参数
  parse(x, n);
  }
 ​
  x.constructor = Big;
 }

 function parse(x, n) {
  var e, i, nl;
 ​
  if (!NUMERIC.test(n)) {
  throw Error(INVALID + 'number');
  }
 ​
  // 判断符号,是正数还是负数
  x.s = n.charAt(0) == '-' ? (n = n.slice(1), -1) : 1;
 ​
  // 判断是否有小数点
  if ((e = n.indexOf('.')) > -1) n = n.replace('.', '');
 ​
  // 判断是否为科学计数法
  if ((i = n.search(/e/i)) > 0) {
 ​
  // 确定指数值
  if (e < 0) e = i;
  e += +n.slice(i + 1);
  n = n.substring(0, i);
  } else if (e < 0) {
 ​
  // 是一个正整数
  e = n.length;
  }
 ​
  nl = n.length;
 ​
  // 确定数字前面有没有0,例如0123这种0
  for (i = 0; i < nl && n.charAt(i) == '0';) ++i;
 ​
  if (i == nl) {
 ​
  // Zero.
  x.c = [x.e = 0];
  } else {
 ​
  // 确定数字后面的0,例如1.230这种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++);
  }
 ​
  return x;
 }

 P.plus = P.add = function (y) {
  var t,
  x = this,
  Big = x.constructor,
  a = x.s,
  // 所有操作均转化为两个Big类的实例进行运算,方便处理
  b = (y = new Big(y)).s;
 ​
  // 判断符号是不是不相等,即一个为正,一个为负
  if (a != b) {
  y.s = -b;
  return x.minus(y);
  }
 ​
  var xe = x.e,
  xc = x.c,
  ye = y.e,
  yc = y.c;
 ​
  // 判断是否某个值是0
  if (!xc[0] || !yc[0]) return yc[0] ? y : new Big(xc[0] ? x : a * 0);
 ​
  // 拷贝一份数组,避免影响原实例
  xc = xc.slice();
 ​
  // 填0来保证运算时的位数相等
  // 注意,reverse函数比unshift函数快
  if (a = xe - ye) {
  if (a > 0) {
  ye = xe;
  t = yc;
  } else {
  a = -a;
  t = xc;
  }
 ​
  t.reverse();
  for (; a--;) t.push(0);
  t.reverse();
  }
 ​
  // 把xc放到一个更长的数组中,方便后续循环加法操作
  if (xc.length - yc.length < 0) {
  t = yc;
  yc = xc;
  xc = t;
  }
 ​
  a = yc.length;
 ​
  // 执行加法操作,将数值保存到xc中
  for (b = 0; a; xc[a] %= 10) b = (xc[--a] = xc[a] + yc[a] + b) / 10 | 0;
 ​
  // 不需要检查0,因为 +x + +y != 0 ,同时 -x + -y != 0
 ​
  if (b) {
  xc.unshift(b);
  ++ye;
  }
 ​
  // 删除结尾的0
  for (a = xc.length; xc[--a] === 0;) xc.pop();
 ​
  y.c = xc;
  y.e = ye;
 ​
  return y;
 };

 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]) return new Big(y.s * 0);
 ​
  // 小数点初始化为x.e+y.e,这是我们在两个小数相乘的时候,小数点的计算规则
  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初始化为xc的长度
  for (i = b; i--;) {
  b = 0;
 ​
  // a是yc的长度
  for (j = a + i; j > i;) {
 ​
  // xc的一位乘以yc的一位,得到最终的结果值,保存下来
  b = c[j] + yc[i] * xc[j - i - 1] + b;
  c[j--] = b % 10;
 ​
  b = b / 10 | 0;
  }
 ​
  c[j] = b;
  }
 ​
  // 如果有进位,那么就调整小数点的位数(增加y.e),否则就删除最前面的0
  if (b) ++y.e;
  else c.shift();
 ​
  // 删除后面的0
  for (i = c.length; !c[--i];) c.pop();
  y.c = c;
 ​
  return y;
 };

 P.round = function (dp, rm) {
  if (dp === UNDEFINED) dp = 0;
  else if (dp !== ~~dp || dp < -MAX_DP || dp > MAX_DP) {
  throw Error(INVALID_DP);
  }
  return round(new this.constructor(this), dp + this.e + 1, rm);
 };
 ​
 function round(x, sd, rm, more) {
  var xc = x.c;
 ​
  if (rm === UNDEFINED) rm = Big.RM;
  if (rm !== 0 && rm !== 1 && rm !== 2 && rm !== 3) {
  throw Error(INVALID_RM);
  }
 ​
  if (sd < 1) {
  // 兜底情况,精度小于1,默认有效值为1
  more =
  rm === 3 && (more || !!xc[0]) || sd === 0 && (
  rm === 1 && xc[0] >= 5 ||
  rm === 2 && (xc[0] > 5 || xc[0] === 5 && (more || xc[1] !== UNDEFINED))
  );
 ​
  xc.length = 1;
 ​
  if (more) {
 ​
  // 1, 0.1, 0.01, 0.001, 0.0001 等等
  x.e = x.e - sd + 1;
  xc[0] = 1;
  } else {
  // 定义为0
  xc[0] = x.e = 0;
  }
  } else if (sd < xc.length) {
 ​
  // xc数组中,在精度之后的纸会被舍弃取整
  more =
  rm === 1 && xc[sd] >= 5 ||
  rm === 2 && (xc[sd] > 5 || xc[sd] === 5 &&
  (more || xc[sd + 1] !== UNDEFINED || xc[sd - 1] & 1)) ||
  rm === 3 && (more || !!xc[0]);
 ​
  // 删除所需精度后的数组值
  xc.length = sd--;
 ​
  // 取整方式判断
  if (more) {
 ​
  // 四舍五入可能意味着前一个数字必须四舍五入,所以这个时候需要填0
  for (; ++xc[sd] > 9;) {
  xc[sd] = 0;
  if (!sd--) {
  ++x.e;
  xc.unshift(1);
  }
  }
  }
 ​
  // 删除小数点后面的0
  for (sd = xc.length; !xc[--sd];) xc.pop();
  }
 ​
  return x;
 }

相关链接:
https://segmentfault.com/a/1190000038322642
https://segmentfault.com/a/1190000019912017?utm_source=tag-newest

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值