欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

big.js?如何解決精度丟失問題源碼解析

 更新時間:2022年10月09日 09:18:12   作者:CatWatermelon  
這篇文章主要為大家介紹了big.js?如何解決精度丟失問題源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

前言

想必大家日常開發(fā)中經(jīng)常碰到小數(shù)相加結(jié)果不準(zhǔn)確的坑,也都知道這是因為精度丟失導(dǎo)致的,更是知道能通過一些庫,比如 big.js、decimal.js 等解決,但是你知道它們是怎么解決的嗎?

今天我?guī)Т蠹乙徊讲椒治?big.js 部分源碼,幫助大家理解這類庫對精度丟失的處理方式。

初窺門徑 —— 打開調(diào)試窗口

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <script src='https://cdn.jsdelivr.net/npm/big.js@6.2.1/big.min.js'></script>
</head>
<body>
    <script>
        new Big(0.1);
    </script>
</body>
</html>

我們可以通過 F12 或者 Ctrl+Shift+I 打開 Chrome DevTool。

在 Sources 標(biāo)簽的 Page 項里,找到未混淆的代碼文件 big.js(xxx.min.js 文件一般是壓縮混淆后的代碼,可讀性不高),這個就是我們要用到的完整源碼了。

知根知底 —— Big 構(gòu)造函數(shù)做的好事

我們先來看看 Big 構(gòu)造函數(shù)做了什么事。

function _Big_() {
    function Big(n) {
        var x = this;
        // 可以通過函數(shù)調(diào)用的形式來創(chuàng)建 Big 對象
        if (!(x instanceof Big)) return n === UNDEFINED ? _Big_() : new Big(n);
        // 區(qū)分是否為 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 && typeof n !== 'bigint') {
                    throw TypeError(INVALID + 'value');
                }
                // Minus zero?
                n = n === 0 && 1 / n < 0 ? '-0' : String(n);
            }
            parse(x, n);
        }
        x.constructor = Big;
    }
    ...
    return Big;
}

可以發(fā)現(xiàn),一開始的時候,構(gòu)造函數(shù)進行了 邊界處理 以及 入?yún)z查 ,隨后通過 parse 函數(shù)處理,最后修正構(gòu)造函數(shù)的指向。

我們先將 this 對象添加進 watches 里,接著 parse 函數(shù)后的位置打個斷點然后刷新。

看看經(jīng)過 parse 函數(shù)生成的數(shù)據(jù):

其中,c 是去除首尾的 0 之后的所有數(shù)字組成的數(shù)組,e 表示用科學(xué)計數(shù)法表示 parse 函數(shù)的入?yún)?x 時 冪的值 ,s 表示正負(fù)(1 表示正數(shù),-1 表示負(fù)數(shù))。

new Big(120) 舉個例子,去除首尾 0 后,c 屬性的值為 [1, 2];

入?yún)?x = 120,用科學(xué)計數(shù)法表示就是 1.2 * 10²,e 的值就是 冪 ,也就是 2,顯然 s = 1。同理,new Big(1.2) 對應(yīng)的值就是 c = [1, 2], e = 0(1.2 * 10º), s = 1

顯然,parse 函數(shù)用來處理數(shù)據(jù),為后續(xù)的運算做準(zhǔn)備。

對細(xì)節(jié)感興趣的小伙伴可以通過 Ctrl + F 快捷搜索研究一下。

到此為止,我們已經(jīng)窺得 Big 構(gòu)造函數(shù)的全貌,接下來我們看看 plus/add 方法做了什么吧。

抽絲剝繭 —— P.plus 源碼分析

老方法,Ctrl+F 搜索 P.plus 后回車,跳轉(zhuǎn)到該方法在文件中所在的位置。

完整源碼

P.plus = P.add = function (y) {
    // 1. 用 x 和 Big 兩個變量分別保存 this(調(diào)用者) 和 Big 構(gòu)造函數(shù)
    var e, k, t,
        x = this,
        Big = x.constructor;
    // 2. 將入?yún)⑥D(zhuǎn)化為 Big 對象
    y = new Big(y);
    // 3. 判斷是否符號不同,如果不同則直接調(diào)用 minus 做減法(1 + (-1)=== 1 - 1)
    if (x.s != y.s) {
        y.s = -y.s;
        return x.minus(y);
    }
    // 4. 分別存儲 x 和 y 各自的小數(shù)點位置以及 number 數(shù)組
    var xe = x.e,
        xc = x.c,
        ye = y.e,
        yc = y.c;
    // 5. Either zero?
    if (!xc[0] || !yc[0]) {
        if (!yc[0]) {
            if (xc[0]) {
                y = new Big(x);
            } else {
                y.s = x.s;
            }
        }
        return y;
    }
    // 6. copy xc 數(shù)組
    xc = xc.slice();
    // 7. 補 0(對齊 x 和 y 的長度)
    // Prepend zeros to equalise exponents.
    // 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();
    }
    // 8. 如果 xc 長度大于 yc,則交換它們
    // Point xc to the longer array.
    if (xc.length - yc.length < 0) {
        t = yc;
        yc = xc;
        xc = t;
    }
    e = yc.length;
    // 9. 相加
    // Only start adding at yc.length - 1 as the further digits of xc can be left as they are.
    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;
    }
    // Remove trailing zeros.
    for (e = xc.length; xc[--e] === 0;) xc.pop();
    y.c = xc;
    y.e = ye;
    return y;
};

步驟 1 —— 變量定義

步驟 1 中使用 xBig 兩個變量分別保存 this(調(diào)用者) 和 Big 構(gòu)造函數(shù)。

步驟 2 —— 處理入?yún)?/h3>

步驟 2 中將入?yún)?y 轉(zhuǎn)為 Big 實例。

步驟 3 —— 判斷符號

步驟 3 中對判斷 xy 的符號是否不同,如果不同的話,會先將 y 取反,調(diào)用 minus 方法處理,因為 一個數(shù)加上一個負(fù)數(shù)相當(dāng)于減去這個負(fù)數(shù)取反(就是 1+(-1)===1-1 的道理)。

這里顯然符號相同,因此繼續(xù)走下去。

步驟 4 —— 保存屬性

步驟 4 中將 xy 的數(shù)字?jǐn)?shù)組和符號位置都保存起來,至于作用是啥我也不知道,我也才調(diào)試到這呢,繼續(xù)往下看。

步驟 5 —— 處理值為 0 的情況

步驟 5 的 if 看來是進不去了,我們自己分析一下吧。

if 的判斷條件是 !xc[0] || !yc[0],誒,這就用到了步驟 4 的變量了。xc 就是 x 的數(shù)字?jǐn)?shù)組(就是 big 實例的 c 屬性),yc 同理。這里大家注意一下,經(jīng)過構(gòu)造函數(shù)調(diào)用 parse 解析之后,實際上是已經(jīng)移除首尾的 0 了,那么 !xc[0]!yc[0] 怎么可能為 true ?那肯定是有段邏輯讓它變成了 0 咯,直接看源碼,果然被我揪到了。

function parse(x, n) {
    ...
    // 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++);
    }
    ...
}

很明顯的,它都給出注釋了,當(dāng)實例化時入?yún)?x 判定為 0 的時候,x.c 和 x.e 都會被置為 0 。那步驟 5 很顯然就是個 提前返回操作 ,直接返回和 0 相加的結(jié)果。

步驟 6 —— 拷貝 xc 防止數(shù)據(jù)污染

步驟 6 中拷貝了一份 xc 數(shù)組,防止數(shù)據(jù)污染。

步驟 7 —— 補 0 對齊

這段代碼的作用是 補0 ,為的是對齊 xy 的長度,這樣方便后續(xù) 按位置進行運算(有沒有做過大整數(shù)加法的小伙伴?里面有個補 0 對齊的操作)。

舉個例子,比如 big(1.2).plus(120),那么 t = xc = [1, 2],執(zhí)行 reverse + push + reverse 后就是 [0, 0, 1, 2] 和 [1, 2, 0, 0]。

步驟 8 —— 比較 xc 和 yc 的長短

步驟 8 中,將 xc 指向長度較長的數(shù)組,yc 指向較短的數(shù)組,且將較短的 yc.lengthe 存儲起來。

步驟 9 —— plus 操作

步驟 9 就是正戲了,到這里真正開始了 plus 操作。

這里有點大整數(shù)相加的意思,為了讓大家理解,我飯都不吃肝了個動圖。

這下大家知道為什么要將 xc 指向較長的數(shù)組,而將 yc 指向較短的數(shù)組了吧,因為較短的數(shù)組前面都是 0,實際上這些 0 都沒必要進行相加處理了。

至此,我們的 P.plus 的源碼就分析結(jié)束了。

終章

本文就到此結(jié)束了,總覽這個解析過程,我們可以發(fā)現(xiàn) big.js 的加法操作,就是 將小數(shù)全部變成整數(shù),然后進行相加 。你不是小數(shù)運算會丟失精度嗎,那我都變成整數(shù)不就好了,計算完我再變回小數(shù),更多關(guān)于big.js 解決精度丟失的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • JS獲取input file絕對路徑的方法(推薦)

    JS獲取input file絕對路徑的方法(推薦)

    下面小編就為大家?guī)硪黄狫S獲取input file絕對路徑的方法(推薦)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2016-08-08
  • 深入解析ECMAScript?2023?中的新數(shù)組方法

    深入解析ECMAScript?2023?中的新數(shù)組方法

    ECMAScript?是一種標(biāo)準(zhǔn)化的腳本語言,它是?JavaScript?的規(guī)范。ECMAScript?2023?是?JavaScript?編程語言的更新,旨在帶來改進并使?JavaScript?程序可預(yù)測和可維護,這篇文章主要介紹了探索?ECMAScript?2023?中的新數(shù)組方法,需要的朋友可以參考下
    2023-12-12
  • 在layui中使用form表單監(jiān)聽ajax異步驗證注冊的實例

    在layui中使用form表單監(jiān)聽ajax異步驗證注冊的實例

    今天小編就為大家分享一篇在layui中使用form表單監(jiān)聽ajax異步驗證注冊的實例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2019-09-09
  • JavaScript對象封裝的簡單實現(xiàn)方法(3種方法)

    JavaScript對象封裝的簡單實現(xiàn)方法(3種方法)

    這篇文章主要介紹了JavaScript對象封裝的簡單實現(xiàn)方法,結(jié)合實例形式分析了3種簡單實現(xiàn)方法與相關(guān)注意事項,需要的朋友可以參考下
    2017-01-01
  • JavaScript如何刪除對象的某個屬性詳析

    JavaScript如何刪除對象的某個屬性詳析

    這篇文章主要給大家介紹了關(guān)于JavaScript如何刪除對象的某個屬性的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-05-05
  • loading動畫特效小結(jié)

    loading動畫特效小結(jié)

    本篇文章主要分享了javascript實現(xiàn)loading動畫特效的示例代碼。具有很好的參考價值,下面跟著小編一起來看下吧
    2017-01-01
  • JavaScript中雙嘆號!!作用示例介紹

    JavaScript中雙嘆號!!作用示例介紹

    !!一般用來將后面的表達式強制轉(zhuǎn)換為布爾類型的數(shù)據(jù),因為javascript是弱類型的語言,所以有時需要強制轉(zhuǎn)換為相應(yīng)的類型
    2014-09-09
  • js實現(xiàn)表單Radio切換效果的方法

    js實現(xiàn)表單Radio切換效果的方法

    這篇文章主要介紹了js實現(xiàn)表單Radio切換效果的方法,涉及javascript實現(xiàn)頁面元素的隱藏與顯示及復(fù)選框的遍歷技巧,具有一定參考借鑒價值,需要的朋友可以參考下
    2015-08-08
  • js與jQuery 獲取父窗、子窗的iframe

    js與jQuery 獲取父窗、子窗的iframe

    本篇文章介紹了js與jQuery 獲取父窗、子窗的iframe。需要的朋友可以過來參考下,希望對大家有所幫助
    2013-12-12

最新評論