在Node.js應(yīng)用程序中處理大數(shù)的操作指南
如何在 Node.js 應(yīng)用程序中處理大數(shù)
計(jì)算機(jī)很難在不損失精度的情況下準(zhǔn)確地表示具有很多個(gè)有效數(shù)字的數(shù)字。當(dāng)你將超過(guò) JavaScript 中最大安全整數(shù)限制的整數(shù)存儲(chǔ)為普通整數(shù)時(shí),它們會(huì)失去精度。
在 JavaScript 生態(tài)系統(tǒng)中,你可以使用 BigInt
來(lái)處理大整數(shù)。但是,你也可以使用具有類似于 BigInt
功能的第三方庫(kù)。
本文將是使用 BigInt
和提供類似功能的流行庫(kù)管理大數(shù)的完整指南。我們還將比較第三方庫(kù)的用例、優(yōu)勢(shì)和劣勢(shì)。
JavaScript 如何編碼數(shù)字?
表示大數(shù)時(shí)精度損失的挑戰(zhàn)并不是 JavaScript 獨(dú)有的。在內(nèi)部,JavaScript 使用雙精度二進(jìn)制浮點(diǎn)格式來(lái)表示數(shù)字。
雙精度二進(jìn)制浮點(diǎn)格式
雙精度二進(jìn)制浮點(diǎn)格式由 IEEE 標(biāo)準(zhǔn) 754 定義。它使用 64 位來(lái)表示帶符號(hào)的浮點(diǎn)數(shù)。用雙精度二進(jìn)制浮點(diǎn)數(shù)表示的數(shù)由符號(hào)、尾數(shù)和指數(shù)三部分組成,如下圖所示:
雙精度二進(jìn)制浮點(diǎn)格式將 64 位分配給這三部分。它使用 1 位編碼符號(hào),11 位編碼偏置指數(shù),52 位編碼尾數(shù)或尾數(shù)。
下面的示例顯示了十進(jìn)制數(shù) -1.7976931348623157e+308
的內(nèi)部雙精度二進(jìn)制浮點(diǎn)數(shù)表示。我使用了 •
字符來(lái)分隔這三個(gè)部分的編碼。
第一位編碼符號(hào)。因?yàn)槲覀冋诰幋a一個(gè)負(fù)數(shù),所以它的值為一。如果我們編碼一個(gè)正數(shù),它的值將為零。隨后的 11 位編碼偏置指數(shù),最后 52 位編碼尾數(shù):
1•11111111110•1111111111111111111111111111111111111111111111111111
計(jì)算機(jī)只能理解二進(jìn)制。因此,在存儲(chǔ)或執(zhí)行數(shù)學(xué)運(yùn)算之前,JavaScript 在內(nèi)部將每個(gè)數(shù)字轉(zhuǎn)換為雙精度二進(jìn)制浮點(diǎn)格式,如上例所示。
然而,某些數(shù)字無(wú)法準(zhǔn)確地用二進(jìn)制表示。因此,當(dāng)你將某些數(shù)字從十進(jìn)制轉(zhuǎn)換為二進(jìn)制再轉(zhuǎn)換回十進(jìn)制時(shí),它們會(huì)失去精度。
同樣,JavaScript 使用固定位數(shù)對(duì)雙精度二進(jìn)制浮點(diǎn)數(shù)的不同部分進(jìn)行編碼。因此,在處理大整數(shù)時(shí),你需要使用第三方庫(kù)或內(nèi)置的 bigint
類型。
JavaScript 中的最小和最大安全整數(shù)
由于雙精度格式將表示尾數(shù)的位數(shù)限制為 53,因此你可以使用的 JavaScript 整數(shù)的精度和準(zhǔn)確性存在限制。
你可以在不丟失精度的情況下使用的最大安全整數(shù)是 2 ** 53 - 1
。它也是使用 Number.MAX_SAFE_INTEGER
訪問(wèn) Number
構(gòu)造函數(shù)的靜態(tài)數(shù)據(jù)屬性:
console.log(2 ** 53 - 1 === Number.MAX_SAFE_INTEGER) // true
還有一個(gè)對(duì)應(yīng)的最小安全整數(shù),其值為-(2**53-1)
。你可以使用 Number.MIN_SAFE_INTEGER
靜態(tài)屬性訪問(wèn)它的值:
console.log(-(2 ** 53 - 1) === Number.MIN_SAFE_INTEGER) // true
你執(zhí)行的任何涉及大于最大安全整數(shù)或小于最小安全整數(shù)的整數(shù)的數(shù)學(xué)運(yùn)算都將導(dǎo)致意外的近似結(jié)果:
const maxSafeInteger = Number.MAX_SAFE_INTEGER; const minSafeInteger = Number.MIN_SAFE_INTEGER; console.log(maxSafeInteger + 1); // 9007199254740992 console.log(maxSafeInteger + 2); // 9007199254740992 console.log(maxSafeInteger + 1 === maxSafeInteger + 2); // true console.log(minSafeInteger - 1); // -9007199254740992 console.log(minSafeInteger - 2); // -9007199254740992 console.log(minSafeInteger - 1 === minSafeInteger - 2); // true
JavaScript 中的正無(wú)窮大和負(fù)無(wú)窮大
就像上面的最小和最大安全整數(shù)一樣,JavaScript 有一個(gè)它可以在內(nèi)部表示的最大數(shù)值。該值為 2 ** 2014 - 1
。你可以使用 Number.MAX_VALUE
數(shù)據(jù)屬性訪問(wèn)它。
JavaScript 使用 Infinity
表示任何超過(guò) Number.MAX_VALUE
的數(shù)值,并使用 -Infinity
表示相應(yīng)的負(fù)值,如下例所示:
console.log(Number.MAX_VALUE * 2); // Infinity console.log(Number.MAX_VALUE * 3); // Infinity console.log(-Number.MAX_VALUE * 3); // -Infinity
盡管 Infinity
在 Node 中是全局的,但你可以使用 Number.POSITIVE_INFINITY
數(shù)據(jù)屬性訪問(wèn)它,并使用 Number.NEGATIVE_INFINITY
數(shù)據(jù)屬性訪問(wèn) -Infinity
。
如何使用 BigInt 在 JavaScript 中管理大整數(shù)
正如前面部分所介紹的那樣,JavaScript 在內(nèi)部使用雙精度格式來(lái)表示數(shù)字。因?yàn)樗褂?53 位來(lái)編碼尾數(shù),所以你可以在 JavaScript 中使用的最大安全整數(shù)是 2**53 - 1
。
要安全地處理大于最大安全整數(shù)的整數(shù),你需要 bigint
類型。它是在不損失精度的情況下操作大整數(shù)的內(nèi)置功能。
你可以通過(guò)將 n 附加到整數(shù)或使用 BigInt
函數(shù)來(lái)創(chuàng)建 bigint
類型。因?yàn)?BigInt
不是構(gòu)造函數(shù),所以不使用 new
關(guān)鍵字調(diào)用它,如下面的示例所示:
const number = 1n; console.log(1n + 2n); // 3n const maxSafeInt = BigInt(Number.MAX_SAFE_INTEGER); console.log(maxSafeInt + 1n); // 9007199254740992n console.log(maxSafeInt + 2n); // 9007199254740993n console.log(maxSafeInt * maxSafeInt); // 81129638414606663681390495662081n
與普通的數(shù)字類型不同,你不能將內(nèi)置的 Math
方法與 BigInt
值一起使用。但是,你可以使用 bigint
類型執(zhí)行基本的數(shù)學(xué)運(yùn)算,例如加法、減法和求冪:
console.log(2n + 3n) // 5n console.log(2n - 3n) // -1n console.log(2n ** 3n) // 8n console.log(4n % 3n) // 1n console.log(BigInt(3) - 4n) // -1n
由于你只能使用 bigint
類型執(zhí)行基本的數(shù)學(xué)運(yùn)算,因此在 JavaScript 中處理大量數(shù)字時(shí),你可能需要為某些用例使用第三方庫(kù)。
在 JavaScript 中管理大數(shù)的庫(kù)
除了內(nèi)置的 bigint
類型之外,還有幾個(gè)第三方庫(kù)可以在 JavaScript 中處理大數(shù)字。其中一些庫(kù)附帶了 BigInt
可能不提供的解決方案。
但是,與任何第三方庫(kù)一樣,使用它們也有缺點(diǎn)。它們伴隨著額外的打包大小、維護(hù)、安全和許可問(wèn)題。
使用 Math.js 管理大數(shù)
Math.js 是一個(gè)免費(fèi)、開(kāi)源且功能豐富的數(shù)學(xué)庫(kù)。你既可以在瀏覽器中使用它,也可以在 Node 運(yùn)行時(shí)環(huán)境中使用它。
雖然它是一個(gè)功能豐富的庫(kù),但在本文中,我們將使用 Math.js 在 Node 運(yùn)行時(shí)環(huán)境中管理大數(shù)。用你的包管理器 npm 安裝它,如下所示:
# npm npm i mathjs # yarn yarn add mathjs #pnpm pnpm add mathjs
安裝 Math.js 后,你可以使用默認(rèn)配置加載和使用它,如下例所示:
const { add, subtract, evaluate }= require('mathjs'); const sum = add(2, 3); const difference = subtract(2, 3); const anotherSum = evaluate('2 + 3'); console.log(sum); // 5 console.log(difference}); // -1 console.log(anotherSum}); // 5
你可以使用自定義配置創(chuàng)建一個(gè) Math.js 實(shí)例,而不是使用默認(rèn)配置的 Math.js 內(nèi)置函數(shù):
const { create, all } = require("mathjs"); const config = {}; const math = create(all, config); console.log(math.add(2, 3)); // 5 console.log(math.pow(2, 3)); // 8 console.log(math.divide(4, 2)); // 2 console.log(math.multiply(2, 3)); // 6
Math.js 具有專門用于處理大數(shù)的 BigNumber
數(shù)據(jù)類型。
在上面的一個(gè)部分中,我們強(qiáng)調(diào)了在使用內(nèi)置數(shù)字類型時(shí),JavaScript 使用 Infinity
表示超過(guò)最大可表示數(shù)值的數(shù)字。
使用 Math.js,你可以表示超過(guò)最大可表示數(shù)的數(shù)字并對(duì)它們執(zhí)行數(shù)學(xué)運(yùn)算。但是,請(qǐng)注意,對(duì) BigNumber
類型執(zhí)行數(shù)學(xué)運(yùn)算比對(duì)普通數(shù)字類型執(zhí)行數(shù)學(xué)運(yùn)算要慢:
const { create, all } = require("mathjs"); const config = {}; const math = create(all, config); const maxValue = math.bignumber(Number.MAX_VALUE); console.log(math.add(maxValue, maxValue)); // 3.5953862697246314e+308 const maxSafeInt = math.bignumber(Number.MAX_SAFE_INTEGER); console.log(math.square(maxSafeInt)); // 8.1129638414606663681390495662081e+31 console.log(math.add(maxSafeInt, maxSafeInt)); // 18014398509481982 console.log(math.subtract(maxSafeInt, maxSafeInt)); // 0 console.log(math.multiply(maxSafeInt, math.bignumber(2))); // 18014398509481982 console.log(math.divide(maxSafeInt, math.bignumber(2))); // 4503599627370495.5 console.log(math.log10(maxSafeInt)); // 15.95458977019100329811178809273377220616031325194798178472905735 console.log(math.pow(maxSafeInt, math.bignumber(2))); // 8.1129638414606663681390495662081e+31
使用 bignumber.js 管理大數(shù)
bignumber.js 是另一個(gè)用于管理任意精度十進(jìn)制和非十進(jìn)制數(shù)的 JavaScript 庫(kù)。它是一個(gè)免費(fèi)、開(kāi)源、MIT 許可的庫(kù),用于處理大數(shù)據(jù)。
它可在瀏覽器、Node 和 Deno 中運(yùn)行。要開(kāi)始使用 bignumber.js,請(qǐng)用 npm 安裝它:
# npm npm i bignumber.js # yarn yarn add bignumber.js #pnpm pnpm add bignumber.js
安裝后,導(dǎo)入并創(chuàng)建一個(gè) BigNumber
構(gòu)造函數(shù)的實(shí)例,它以數(shù)字、字符串或 BigNumber
類型作為參數(shù)并返回一個(gè)對(duì)象。
在下面的示例中,我使用 commonjs 語(yǔ)法導(dǎo)入 bignumber.js。它還支持 ES 語(yǔ)法。如果你打算在沒(méi)有 JavaScript 打包器的情況下在瀏覽器環(huán)境中使用 bignumber.js,你也可以通過(guò) CDN 訪問(wèn)它:
const BigNumber = require("bignumber.js"); const distanceOfTheSun = new BigNumber('1.49597870700e11'); // console.log(distanceOfTheSun) // BigNumber { s: 1, e: 11, c: [ 149597870700 ] } console.log(distanceOfTheSun.valueOf()) // 149597870700
當(dāng)使用內(nèi)置數(shù)字類型時(shí),JavaScript 會(huì)將任何大于 Number.MAX_VALUE
的數(shù)值表示為 Infinity
。但是,對(duì)于 bignumber.js,你可以使用任何大于 Number.MAX_VALUE
的值。
在下面的示例中,我通過(guò)將 Number.MAX_VALUE
作為字符串傳遞并計(jì)算其平方來(lái)創(chuàng)建 BigNumber
的實(shí)例。如果你使用內(nèi)置的 JavaScript 數(shù)字類型做同樣的事情,你會(huì)得到 Infinity
:
const BigNumber = require("bignumber.js"); console.log(Number.MAX_VALUE); // 1.7976931348623157e+308 console.log(Number.MAX_VALUE ** 2) // Infinity const maxValue = new BigNumber(Number.MAX_VALUE.toString()); const square = maxValue.exponentiatedBy("2"); console.log(square.valueOf()); // 3.23170060713109998320439596646649e+616 const squareRoot = square.squareRoot(); console.log(squareRoot.valueOf()); // 1.7976931348623157e+308 console.log(squareRoot.isEqualTo(maxValue)); // true
但是,當(dāng)處理無(wú)法在 JavaScript 中表示的如此大的數(shù)字時(shí),請(qǐng)使用 toString
或 valueOf
方法以字符串形式訪問(wèn)計(jì)算結(jié)果。
toNumber
方法會(huì)將你的計(jì)算結(jié)果強(qiáng)制轉(zhuǎn)換為 JavaScript 數(shù)字類型。你仍然會(huì)遇到上面突出顯示的相同 JavaScript 大數(shù)問(wèn)題。你的答案將失去精度,或者 JavaScript 會(huì)將其表示為 Infinity
。
雖然我們?cè)诒疚闹械哪繕?biāo)是使用 bignumber.js 包來(lái)處理大數(shù),但 bignumber.js 也可以處理相應(yīng)的小數(shù)。它有幾個(gè)我沒(méi)有在這里突出展示的內(nèi)置方法。你可以通過(guò)查看文檔以了解其他內(nèi)置函數(shù)。
使用 JS Big Decimal 管理大數(shù)
JS Big Decimal 是另一個(gè)可用于處理大數(shù)的 JavaScript 庫(kù)。與上面的同類庫(kù)不同,JS Big Decimal 的包體積小,并且功能有限。你可以使用它來(lái)管理大、小十進(jìn)制數(shù)。
根據(jù)你的包管理器,使用以下命令之一從 npm 包倉(cāng)庫(kù)安裝 JS Big Decimal:
# npm npm i js-big-decimal # yarn yarn add js-big-decimal #pnpm pnpm add js-big-decimal
與其他兩個(gè)包一樣,導(dǎo)入 BigDecimal
構(gòu)造函數(shù)并創(chuàng)建一個(gè)實(shí)例,如下例所示。 BigDecimal
構(gòu)造函數(shù)將數(shù)字或字符串作為參數(shù)并返回 BigDecimal
對(duì)象。
然后,你可以使用 getValue
方法以字符串形式訪問(wèn)數(shù)字的值。或者,如果要格式化輸出,請(qǐng)使用 getPrettyValue
:
const BigDecimal = require("js-big-decimal"); const value = new BigDecimal('23'); console.log(value.add(new BigDecimal(2)).getValue())
JS Big Decimal 具有執(zhí)行基本數(shù)學(xué)運(yùn)算的函數(shù),例如加法、減法、乘法和除法。下面的代碼演示了如何使用它們來(lái)處理大數(shù)據(jù):
const BigDecimal = require("js-big-decimal"); const maxSafeInt = new BigDecimal(Number.MAX_SAFE_INTEGER.toString()); const divisor = new BigDecimal("2"); console.log(maxSafeInt.getPrettyValue()); // 9,007,199,254,740,991 const sum = maxSafeInt.add(maxSafeInt); const quotient = maxSafeInt.divide(divisor); const diff = maxSafeInt.subtract(quotient); const product = quotient.multiply(divisor); console.log(sum.getValue()); // 18014398509481982 console.log(quotient.getPrettyValue()); // 4,503,599,627,370,495.50000000 console.log(diff.getPrettyValue()); // 4,503,599,627,370,495.50000000 console.log(product.getPrettyValue()); // 9,007,199,254,740,991
比較在 JavaScript 中管理大數(shù)的庫(kù)
并非所有庫(kù)都是完美的。每個(gè)第三方庫(kù)都有用例、優(yōu)點(diǎn)和缺點(diǎn)。讓我們通過(guò)對(duì)比它們的優(yōu)缺點(diǎn)并探索 GitHub 星級(jí)和問(wèn)題、包大小和 npm 下載等指標(biāo)來(lái)比較上述第三方包。
GitHub stars 等指標(biāo)與社交媒體點(diǎn)贊類似。你可以將其用作第三方庫(kù)受歡迎程度的指標(biāo)。然而,它并沒(méi)有告訴你太多關(guān)于質(zhì)量的信息。
同樣,npm 下載統(tǒng)計(jì)數(shù)據(jù)也遠(yuǎn)非精確。根據(jù) npm,下載計(jì)數(shù)是 tarball 文件的 HTTP 200 響應(yīng)服務(wù)的數(shù)量。因此,下載計(jì)數(shù)包括構(gòu)建服務(wù)器、鏡像和機(jī)器人的自動(dòng)下載。雖然 npm 下載計(jì)數(shù)不是庫(kù)活躍用戶的準(zhǔn)確度量,但你可以使用它來(lái)對(duì)包進(jìn)行比較。
Math.js | bignumber.js | JS Big Decimal | |
---|---|---|---|
壓縮后的包體積 | 187.93KB | 8.09KB | 3.88KB |
依賴數(shù)量 | 9 | 0 | 0 |
GitHub stars | 13.1k | 6k | 96 |
積極維護(hù) | Yes | Yes | Yes |
文檔 | Good | Good | Good |
許可證 | Apache-2.0 | MIT | MIT |
npm 周下載量 | 502,656 | 7,114,325 | 25,204 |
上述所有第三方庫(kù)都是免費(fèi)的開(kāi)源庫(kù),具有許可。在這三者中,Math.js 是一個(gè)功能豐富的通用數(shù)學(xué)庫(kù),而另外兩個(gè)是為管理大數(shù)字而創(chuàng)建的。
因此,Math.js 具有最大的 Gzipped 包大小。但是,如果你使用像 webpack 這樣的打包器,它是可通過(guò) tree-shake 優(yōu)化的。 Math.js 和 bignumber.js 都帶有幾個(gè)用于管理大數(shù)和對(duì)它們執(zhí)行數(shù)學(xué)運(yùn)算的特性。
另一方面,JS Big Decimal 具有最小的包大小。但是,它的功能也最少。它只能執(zhí)行基本的數(shù)學(xué)運(yùn)算。
小結(jié)
JavaScript 在內(nèi)部使用 64 位雙精度二進(jìn)制浮點(diǎn)格式來(lái)表示數(shù)字。它分配一位表示符號(hào),11 位表示指數(shù),53 位表示尾數(shù)。
JavaScript 分配固定位來(lái)表示雙精度浮點(diǎn)數(shù)的不同部分。因此,它近似于安全整數(shù)范圍之外的整數(shù)。同樣,它使用 Infinity
表示大于 Number.MAX_VALUE
的數(shù)值,使用 -Infinity
表示它們對(duì)應(yīng)的負(fù)值。
盡管內(nèi)置的 BigInt
對(duì)于處理大于最大安全整數(shù)或小于最小安全整數(shù)的整數(shù)很有用,但它是有一定局限的,因?yàn)槟阒荒軋?zhí)行基本的數(shù)學(xué)運(yùn)算,例如加法、減法、乘法和求冪。你不能將它與內(nèi)置 Math
對(duì)象的方法一起使用;這樣做會(huì)引發(fā)錯(cuò)誤。
要在 JavaScript 中處理大數(shù)字而不會(huì)遇到上述限制,你需要第三方庫(kù),例如 Math.js、bignumber.js 和 JS Big Decimal。盡管大多數(shù)第三方庫(kù)都有局限性,正如上面強(qiáng)調(diào)的那樣,但它們具有輕而易舉處理大數(shù)的功能。
以上就是在Node.js應(yīng)用程序中處理大數(shù)的操作指南的詳細(xì)內(nèi)容,更多關(guān)于Node.js 處理大數(shù)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
在Debian(Raspberry Pi)樹(shù)莓派上安裝NodeJS的教程詳解
在樹(shù)莓派上運(yùn)行NodeJS并不需要特別的配置,你只需要確??梢杂胦penssh遠(yuǎn)程連接到你的樹(shù)莓派就ok了,關(guān)于在Debian(Raspberry Pi)樹(shù)莓派上安裝NodeJS的方法,大家可以通過(guò)本文了解下2017-09-09node連接kafka2.0實(shí)現(xiàn)方法示例
這篇文章主要介紹了node連接kafka2.0,nodejs連接kafka2.0的實(shí)現(xiàn)方法,結(jié)合實(shí)例形式分析了kafka2.0的功能、原理、以及node.js連接kafka2.0的具體實(shí)現(xiàn)技巧,需要的朋友可以參考下2023-05-05詳解nodejs中exports和module.exports的區(qū)別
本文主要介紹了exports 和 module.exports 的區(qū)別。具有很好的參考價(jià)值,下面跟著小編一起來(lái)看下吧2017-02-02Node.js中JWT實(shí)現(xiàn)身份驗(yàn)證的詳細(xì)步驟
身份驗(yàn)證是Web開(kāi)發(fā)的重要組成部分,JWT由于其簡(jiǎn)單性,安全性和可擴(kuò)展性,已成為在Web應(yīng)用程序中實(shí)現(xiàn)身份驗(yàn)證的流行方法,在這篇文章中,我將指導(dǎo)你在Node.js應(yīng)用程序中使用MongoDB進(jìn)行數(shù)據(jù)存儲(chǔ)來(lái)實(shí)現(xiàn)JWT身份驗(yàn)證,需要的朋友可以參考下2024-12-12node.js中stream流中可讀流和可寫流的實(shí)現(xiàn)與使用方法實(shí)例分析
這篇文章主要介紹了node.js中stream流中可讀流和可寫流的實(shí)現(xiàn)與使用方法,結(jié)合實(shí)例形式分析了node.js stream流可讀流和可寫流基本分類、原理、定義、使用方法及相關(guān)注意事項(xiàng),需要的朋友可以參考下2020-02-02一會(huì)帶你學(xué)會(huì)用Webpack搭建開(kāi)發(fā)環(huán)境并打包代碼
這篇文章主要給大家介紹了關(guān)于如何用Webpack搭建開(kāi)發(fā)環(huán)境并打包的相關(guān)資料,webpack是一個(gè)現(xiàn)代JavaScript應(yīng)用程序的靜態(tài)模塊打包器(module bundler),需要的朋友可以參考下2023-08-08Node.js 使用 Express-Jwt和JsonWebToken 進(jìn)行Token身份
這篇文章主要介紹了Node.js 使用 Express-Jwt和JsonWebToken 進(jìn)行Token身份驗(yàn)證的操作方法,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧2024-08-08Node 使用express-http-proxy 做api網(wǎng)關(guān)的實(shí)現(xiàn)
這篇文章主要介紹了Node 使用express-http-proxy 做api網(wǎng)關(guān)的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-10-10