日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > windows >内容正文

windows

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

發(fā)布時間:2023/11/30 windows 32 coder
生活随笔 收集整理的這篇文章主要介紹了 图文剖析 big.js 四则运算源码 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

big.js,一個小型、快速的用于任意精度的十進制算術的JavaScript 庫。

big.js 用于解決平常項目中進行算術運算時精度丟失引起的結果不準確的問題。和 big.js 類似的兩個庫 bignumber.js 和 decimal.js 也都是出自同一作者(MikeMcl)之手。

作者在 這里 詳細說明了他們之間的區(qū)別

big.js?是最小的任意精度的計算庫。big.js?是三者中最小也最簡單的,它只有?bignumber.js?一半的方法,不到?bignumber.js?的一半大。

bignumber.js?和?decimal.js?存儲值的進制比?big.js?更高,因此當操作大量數(shù)字時,前兩者的速度會更快。

bignumber.js?可能更適合金融類應用,因為用戶不用擔心丟失精度,除非使用了涉及除法的操作。

這篇文章分別就 big.js 的解析函數(shù),以及加減乘除運算的源碼進行剖析,了解作者的設計思路。在四則運算的源碼中,相比加減乘,除法運算最為復雜。

用法

創(chuàng)建 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

構造函數(shù)

構造函數(shù)中關鍵代碼如下

function Big(n) {
  var x = this;

  // 使用構造函數(shù)前面可以不帶 new 關鍵字
  if (!(x instanceof Big)) return n === UNDEFINED ? _Big_() : new Big(n);

  // 如果傳進來的參數(shù)已經(jīng)是 Big 的實例對象,則復制一份,否則使用 parse 函數(shù)創(chuàng)建一個實例對象
  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);
  }

使用構造函數(shù)前面可以不帶 new 關鍵字

如果傳進來的參數(shù)已經(jīng)是 Big 的實例對象,則將實例對象的屬性復制一份,否則使用 parse 函數(shù)為實例對象創(chuàng)建屬性。

parse 函數(shù)

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 函數(shù)會為實例對象添加三個屬性;

  • x.s,表示數(shù)字的符號,即是正數(shù)還是負數(shù),即正負值,若是正數(shù),x.s = 1,負數(shù)則為 -1
  • x.e,表示數(shù)字對應的指數(shù)表示法的指數(shù),比如 n = 1234 的指數(shù)為 3
  • x.c,數(shù)字數(shù)組,比如 1234 轉換后是 [1,2,3,4]
1234 會被轉化為 

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

這種表示,和 IEEE 754 雙精度浮點數(shù)的存儲方式 很類似,而 JavaScript 的 Number類型就是一個雙精度 64 位二進制格式 IEEE 754?值使用 64 位來表示 3 個部分:

  • 1 位用于表示符號(sign) (正數(shù)或者負數(shù))
  • 11 位用于表示指數(shù)(exponent) (-1022 到 1023)
  • 52 位用于表示尾數(shù)(mantissa) (表示 0 和 1 之間的數(shù)值)

下面分析 parse 函數(shù)轉化的詳細過程,以 Big('123400')Big('0.1234')Big('100e2') 為例

注意:Big('100e2') 中 100e2 以字符串形式傳進來才能檢測到 e ,Number形式的 Big(100e2),執(zhí)行 parse 前會被轉化為 Big(10000)

  1. 校驗傳入的值,只允許數(shù)字,'.1',指數(shù)形式的寫法。比如 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('.', ''); 是否含有小數(shù)點,如果是,則刪除小數(shù)點,并將 e 的初始值設為小數(shù)點的位置
Big('123400') => x.e = -1 , n = 123400
Big('-0.1234') => x.e = 1 , n = 01234
Big('100e2') => x.e = -1 , n = 100e2
  1. 如果數(shù)字是科學表示法,比如 100e2 ,e 的位置是 3,e 后面的指數(shù)是 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 后面緊跟的指數(shù)系數(shù) = 3 + 2 = 5
n = 100e2  =>  n = 100
  1. nl = n.length; nl 表示傳進來的數(shù)字的長度
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; 確定數(shù)字是否有前置 0 ,這里的 i 表示第一個不為 0 的數(shù)字的位置,也可以表示數(shù)字前面有多少個 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 的數(shù)字的位置
  for (; nl > 0 && n.charAt(--nl) == '0';);
  x.e = e - i - 1;
  x.c = [];

  // 傳進來的數(shù)字,排除掉前置 0 和尾隨 0 后,轉換為數(shù)字數(shù)組
  for (e = 0; i <= nl;) x.c[e++] = +n.charAt(i++);
}


Big('123400') 
//因為默認 e 是 n.length,而Big指數(shù)表示是 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 的數(shù)字的位置  =>  nl = 3
i = 0
x.c => 選取 n 中從 i 到 nl 的數(shù)字組成數(shù)組 [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 的數(shù)字的位置  =>  nl = 4
i = 1
x.c => 選取 n 中從 i 到 nl 的數(shù)字組成數(shù)組 [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 的數(shù)字的位置  =>  nl = 0
i = 0
x.c => 選取 n 中從 i 到 nl 的數(shù)字組成數(shù)組 [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 函數(shù)邏輯結束,接下來分別剖析下加減乘除運算;

加法

源碼

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();

    // 前面加上零使指數(shù)均衡
    // 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 存放長度更長的數(shù)字
    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. 其中兩個數(shù)字是不是 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. 比較指數(shù)冪差,較小的一方,在前面補零,方便后續(xù)加法操作;并且將指數(shù)冪較大的一方,作為兩數(shù)相加的結果的指數(shù)冪的初始值。
if (e = xe - ye) {
  if (e > 0) {
    ye = xe; // 將指數(shù)冪較大的一方,作為兩數(shù)相加的結果的指數(shù)冪的初始值
    t = yc;
  } else {
    e = -e;
    t = xc;
  }

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

比如 1234 + 12 
1234 在實例對象上是以數(shù)字數(shù)組形式表示 [1,2,3,4]
12 則是 [1,2]
為方便后續(xù)數(shù)組按照位置進行加法運算,這里需要給 12 補零
[1,2,3,4]
    +
[0,0,1,2]
  1. xc 存放長度更長的數(shù)字
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 長度,執(zhí)行下面循環(huán)體
  • (xc[--e] = xc[e] + yc[e] + k) 計算 xc[e] 加上 yc[e] 加上上一次計算結果進位的值;
  • 隨后 xc[--e] 保存計算后的進位的數(shù)值,e--
  • 最后 xc[e] 保存計算后的個位數(shù)值

上面過程用圖例表示如下

減法

源碼

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;
    }

    // 比較兩數(shù)指數(shù)冪大小,給指數(shù)冪小的一方補零,方便后續(xù)相減;
    // 比如 1234 - 23  parse函數(shù)解析后 => [1,2,3,4] - [2,3]  為了使 [2,3] 對應十位,個位
    // 在前面補 0 ,即 [1,2,3,4] - [0,0,2,3]
    // 再比如 66 - 233  parse函數(shù)解析后 => [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 {

      // 若指數(shù)冪相等,不需要補零,則比較兩數(shù)大小,從最大位開始比較;
      // 比如 [2,3,4] 和 [1,2,3] 最大位是百位,若百位的數(shù)字不相等,則可得出孰大孰小
      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;
        }
      }
    }
    
    // 對于被減數(shù) x 和減數(shù) y
    // 如果 x - y < 0,則交換兩數(shù),并改變符號;比如 2 - 4 = -(4-2)
    if (xlty) {
      t = xc;
      xc = yc;
      yc = t;
      y.s = -y.s;
    }

    // 如果被減數(shù)的數(shù)字數(shù)組長度小于減數(shù),則給被減數(shù)的末尾添加 0 
    // 比如 12 - 0.0009  parse函數(shù)解析后 => [1,2] - [0,0,0,0,9]
    // 因為 9 是小數(shù)后幾位,相應的需要給 [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 ,并減去相應指數(shù)冪
    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;
  };

減法前面的邏輯和加法類似,這里不再贅述,已在上面代碼注釋中說明,下面是減法的核心邏輯

// 從 被減數(shù) xc 中減去減數(shù) yc
// a 是 xc 和 yc 的冪的差值,j 是 yc 的長度,這里循環(huán)條件用 j > a,表示循環(huán) j-a 次
// 比如 120 - 9  =>  [1,2,0]-[0,0,9] 指數(shù)冪差是 2 ,減數(shù)數(shù)字數(shù)組長度是 3 ,則只需要循環(huán) 3-2=1 次
// 比如 120 - 0.009 => [1,2,0,0,0,0]-[0,0,0,0,0,9] 指數(shù)冪差是 5 ,減數(shù)數(shù)字數(shù)組長度是 6 ,則只需要循環(huán) 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 表示被減數(shù),yc 表示減數(shù)

1、若 xc 末尾項大于等于 yc 末尾項,比如 [1,2,3]和[0,0,2],則直接相減。

2、若 xc 末尾項小于 yc 末尾項,則執(zhí)行以下邏輯

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

上面代碼表示從 當前進行相減運算的元素的位置(j) 往前遍歷被減數(shù) xc 每個元素,當元素值為 0 時,將值改為 9,直至上一個元素值不為 0 ,循環(huán)結束。

至此,減法邏輯結束。

乘法

源碼

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;
    }

    // 初始化結果的指數(shù)
    y.e = i + j;

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

    // 用 0 初始化結果數(shù)組
    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;
    }

    // 如果有最終進位,則增加結果的指數(shù),否則刪除頭部的 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;) {

    // 當前數(shù)字位置的總和,加上進位
    b = c[j] + yc[i] * xc[j - i - 1] + b;
    c[j--] = b % 10;

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

  c[j] = b;
}

描述的其實就是以前老師教我們在紙上乘法運算的過程:

123*12 來舉例子分析上面這段代碼

  • xc 是乘數(shù) [1,2,3],yc 是被乘數(shù) [1,2],b 是 yc 長度,a 是 xc 長度

  • c 是保存結果的數(shù)組,定義的長度是 a+b

兩個數(shù)相乘得到的結果長度可能是 a+b,也有可能是 a+b-1。所以后面需要刪除數(shù)組頭部的 0

  1. for (i = b; i--;) 首先是外層循環(huán),從數(shù)組長度較短的被乘數(shù)開始循環(huán),將 b 賦值給 i,i 充當 yc 的長度,而 b 用來保存進位的值
  2. b = 0 定義進位的值
  3. for (j = a + i; j > i;) 內層乘數(shù)(123)的循環(huán),這里的 j 表示在結果數(shù)組 c 中的位置

for (j = a + i; j > i;) 實際上就是 for ( j = 乘數(shù)長度 + 當前被乘數(shù)數(shù)字的位置 ),這里是因為當?shù)诙喭鈱友h(huán)時,123 * 1 的時候,1 是 12 的 十位,所以在 j 也應該從十位開始保存計算結果。

第一輪外層循環(huán)

第二輪外層循環(huán)

  1. b = c[j] + yc[i] * xc[j - i - 1] + b 當前數(shù)字位置的總和,加上進位

  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 是被除數(shù),b 是除數(shù),下面依次分析上面代碼

  1. if (dp !== ~~dp || dp < 0 || dp > MAX_DP) 判斷 dp 是不是大于 0 的整數(shù),并且小于 MAX_DP,這里的 dp 可以自己設置
Big.DP = 30
  1. 除數(shù)為 0 則拋出錯誤。
if (!b[0]) {
  throw Error(DIV_BY_ZERO);
}
  1. 被除數(shù)是 0 則返回值為 0 的實例對象
if (!a[0]) {
  y.s = k;
  y.c = [y.e = 0];
  return y;
}
  1. 接下來是除法運算邏輯,定義變量的那一段不貼了,直接看 do while 循環(huán)
do {

  // n 是循環(huán)次數(shù),表示從當前位置的余數(shù)中可以分出多少個除數(shù)來,也就是當前位置的商。
  for (n = 0; n < 10; n++) {

    // 比較除數(shù)和余數(shù)大小
    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;
        }
      }
    }

    // 除數(shù)小于余數(shù),則繼續(xù)從余數(shù)中減去除數(shù)
    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 數(shù)組保存商
  qc[qi++] = cmp ? n : ++n;

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

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

這個循環(huán)做了這些事情:以 1234 / 9 為例;

  • 將當前位置的余數(shù) 1 和除數(shù) 9 比較大小,(一開始余數(shù)取的是被除數(shù)的前面 n 位,n 和除數(shù)的長度大小相同,所以取的是 1 )。先比較長度,長度相同再比較大小。
  • 若除數(shù)大于當前位置余數(shù),則跳出循環(huán)(當前循環(huán)次數(shù)即為當前位置的商,9 > 1 ,那么當前循環(huán)次數(shù)為 0 ,即當前位置商為 0 )
  • 然后保存商 qc[qi++] = cmp ? n : ++n;
  • 最后更新當前余數(shù),除數(shù)大于余數(shù)時,則當前余數(shù)向后借一位,余數(shù)就由 1 變?yōu)榱?12
if (r[0] && cmp) r[rl] = a[ai] || 0;
  else r = [a[ai]];
  • 若除數(shù)小于當前余數(shù),則繼續(xù)從余數(shù)中減去除數(shù) (開始減法 for 循環(huán))

  • 然后再次保存商 qc[qi++] = cmp ? n : ++n;
  • 然后再次更新當前余數(shù),除數(shù)大于余數(shù)時,則當前余數(shù)向后借一位,余數(shù)就由 3 變?yōu)榱?33
  • 當商數(shù)組的長度沒有達到指定的精度總和,繼續(xù)上面的步驟,直至循環(huán)結束;

指定的精度總和指的是 Big.DP(默認20) + (被除數(shù)的指數(shù)-除數(shù)的指數(shù)); 1234 / 9 的指定精度總和是 23。

  1. 商數(shù)組長度大于 1 的情況下,刪除數(shù)組前面的 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 用數(shù)組存儲值,類似 高精度計算,只不過 big.js 是數(shù)組中每個位置存儲一個值,然后對每個位置進行運算;而對超級大的數(shù)字(數(shù)百或數(shù)千位數(shù)值時),big.js 算術運算不如 bignumber.js 快。

例如,bignumber.js 將數(shù)字1234.56789的數(shù)字存儲為[1234,56789000000000] ,即以兩個1e14為基數(shù)的數(shù)組形式存儲,而 big.js 存儲的數(shù)字與[1,2,3,4.5,6,7,8,9]相同,即以9個10為基數(shù)的數(shù)組形式存儲。前者的算術運算可能更快,因為需要處理的元素較少。在實踐中,這可能只有在使用數(shù)百或數(shù)千位數(shù)值時才會有所不同。

在使用 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 后
//結果數(shù)組保存的是 999999999999999999999999
//超過了 Number 編碼的可以表示的準確精度范圍,則會舍入為 1

總結

本文剖析了 big.js 解析函數(shù)源碼,四則運算源碼,分別用圖文詳細描述了運算過程,一步步還原了作者的構思。有不正確的地方或者不同見解還請各位大佬提出來。

總結

以上是生活随笔為你收集整理的图文剖析 big.js 四则运算源码的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內容還不錯,歡迎將生活随笔推薦給好友。