JavaScript
單元:變數與資料型別(Variables & Data Types)
主題:BigInt
簡介
在日常開發中,我們大多使用 Number 來表示整數與浮點數,然而 JavaScript 的 Number 只能安全地表示到 ±2⁵³‑1(約 9 兆)之內的整數。當程式需要處理更大或更精確的整數時(例如金融計算、加密演算法、天文數據),Number 會產生精度損失,導致結果不可靠。
ECMAScript 2020 正式引入了 BigInt,讓開發者可以在 JavaScript 中直接操作任意長度的整數,且不會受到 53 位安全整數的限制。掌握 BigInt 的使用方式,能讓你的程式在處理大數、精密計算時更加穩定,也為未來的高精度需求打下基礎。
本篇文章將從概念說明、實作範例、常見陷阱到最佳實踐,完整介紹 BigInt 的使用方式,讓「初學者」也能快速上手,「中級開發者」能在專案中安全運用。
核心概念
1. 什麼是 BigInt
BigInt 是一種原始資料型別(primitive),用來表示 任意大小的整數。與 Number 不同,BigInt 可以超過 2⁵³‑1 的範圍,且在運算時保持 整數精度。
注意:
BigInt只能表示整數,不能直接與Number混用,除非先做型別轉換。
2. 建立 BigInt
有兩種常見的建立方式:
// 1. 在字面量後加上 n(最常見)
const big1 = 123456789012345678901234567890n;
// 2. 使用全域建構函式 BigInt()
const big2 = BigInt("123456789012345678901234567890");
// 3. 由 Number 轉換(僅在安全範圍內)
const big3 = BigInt(12345); // 轉換結果為 12345n
- 字面量
n是最直觀且最易讀的寫法。 BigInt()可接受字串或數字,適合從外部資料(例如 JSON、使用者輸入)建立大數。
3. 基本運算
BigInt 支援四則運算、比較運算與位元運算(除法會向下取整)。以下範例示範常見操作:
const a = 98765432109876543210n;
const b = 12345678901234567890n;
// 加法、減法、乘法
const sum = a + b; // 111111111011111111100n
const diff = a - b; // 86419753208641975320n
const prod = a * b; // 1219326311370217952261850327336229230n
// 除法(向下取整)與餘數
const quot = a / b; // 8n
const mod = a % b; // 9n
// 比較運算
console.log(a > b); // true
console.log(a === 98765432109876543210n); // true
小技巧:如果需要精確的除法結果(包含小數),必須先將
BigInt轉成Number或使用第三方函式庫,因為BigInt本身不支援小數。
4. 與 Number 的相互轉換
const big = 9007199254740993n; // 超過 Number 安全上限
const num = Number(big); // 9.007199254740993e+15(仍保留精度,但已是 Number)
// 從 Number 轉成 BigInt(僅在安全範圍內)
const safeNum = 123456;
const safeBig = BigInt(safeNum); // 123456n
- 從
BigInt轉Number可能會失去精度,僅在你確定結果仍在安全範圍時使用。 - 從
Number轉BigInt必須先確保原始Number為整數且在安全範圍內,否則會拋出RangeError。
5. BigInt 與 JSON
JSON.stringify() 會自動把 BigInt 轉成字串,否則會拋出 TypeError:
const data = { id: 12345678901234567890n };
try {
const json = JSON.stringify(data); // TypeError: Do not know how to serialize a BigInt
} catch (e) {
console.error(e.message);
}
// 正確做法:先手動轉成字串
const safeJson = JSON.stringify({ id: data.id.toString() });
console.log(safeJson); // {"id":"12345678901234567890"}
在與後端或本地儲存交換資料時,建議將 BigInt 轉成字串,再在接收端使用 BigInt() 重新轉回。
程式碼範例
範例 1:計算階乘(Factorial)
/**
* 使用 BigInt 計算任意正整數的階乘
* @param {number|bigint} n
* @returns {bigint}
*/
function factorial(n) {
let result = 1n;
// 允許傳入 Number,先轉成 BigInt
let i = BigInt(n);
for (; i > 1n; i--) {
result *= i;
}
return result;
}
console.log(factorial(25)); // 15511210043330985984000000n
此函式即使計算 100! 這樣的極大數,也不會產生精度問題。
範例 2:大數加密雜湊(簡易示例)
/**
* 將字串轉成對應的 BigInt,模擬簡易的雜湊演算法
* @param {string} str
* @returns {bigint}
*/
function simpleHash(str) {
let hash = 0n;
for (const ch of str) {
// 取字元 Unicode 編碼,累加後乘上一個大質數
hash = (hash + BigInt(ch.codePointAt(0))) * 31n;
}
// 取模以限制長度(此處使用 2^64 為例)
return hash % (2n ** 64n);
}
console.log(simpleHash("BigInt 範例")); // 1234567890123456789n(示意值)
此例說明 BigInt 在加密、雜湊等需要大數運算的領域 的可行性。
範例 3:處理金融金額(避免精度錯誤)
// 假設每筆交易金額以「分」為單位,使用 BigInt 保障精度
const transactions = [
1999n, // $19.99
4500n, // $45.00
12345678901234567890n // 超大金額
];
function totalAmount(list) {
return list.reduce((sum, v) => sum + v, 0n);
}
const totalCents = totalAmount(transactions);
console.log(`總金額:${totalCents} 分 (${Number(totalCents) / 100} 元)`);
// 若金額過大,Number(totalCents) 可能失去精度,建議直接使用字串輸出
console.log(`總金額(字串):${totalCents.toString()} 分`);
透過 BigInt 儲存最小貨幣單位(分或厘),可以避免浮點數四捨五入的問題。
範例 4:與 Date 結合計算 Unix 時間戳(毫秒)
// 取得目前時間的毫秒時間戳(Number),轉成 BigInt 以做後續大數運算
const nowMs = BigInt(Date.now());
// 假設要計算 10 年後的時間戳
const tenYearsMs = nowMs + 10n * 365n * 24n * 60n * 60n * 1000n;
console.log(new Date(Number(tenYearsMs)).toISOString());
// 輸出:2034-11-19T...(依實際執行時間而定)
在需要 跨越極長時間範圍(例如天文觀測、歷史資料)時,使用 BigInt 可避免毫秒級溢位。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方式 |
|---|---|---|
Number 與 BigInt 直接比較或運算 |
例如 1n + 1 會拋出 TypeError。 |
必須先將雙方轉成相同型別:Number(1n) + 1 或 1n + BigInt(1)。 |
| 除法結果被截斷 | BigInt 的除法只返回整數部份(向下取整)。 |
若需要小數,先轉成 Number 或使用外部高精度函式庫(如 decimal.js)。 |
| JSON 序列化失敗 | JSON.stringify({v: 10n}) 直接拋錯。 |
手動 toString() 後再序列化,或使用自訂 replacer。 |
| 意外的隱式型別轉換 | == 會嘗試把 BigInt 轉成 Number,可能產生精度問題。 |
建議使用嚴格相等 ===,或明確轉型。 |
| 性能考量 | BigInt 計算較 Number 慢,特別是大量迭代時。 |
僅在需要大數或精度保證時使用,否則保持 Number。 |
最佳實踐
- 明確宣告型別:使用
n後綴或BigInt()建立,避免混用。 - 封裝轉型邏輯:在函式入口處統一把參數轉成
BigInt,減少重複程式碼。 - 避免在大量迴圈中頻繁轉型:一次性轉換後再進行運算。
- 使用字串儲存:與外部系統交換大數時,統一使用字串格式。
- 測試邊界值:特別是與
Number交互的情況,寫單元測試驗證精度。
實際應用場景
- 金融系統:交易金額、利息計算、加密貨幣區塊高度等,需要精確到最小單位且可能超過
Number安全範圍。 - 區塊鏈與加密:區塊高度、哈希值、簽名運算常以 256 位或更長的整數表示,
BigInt為天然匹配。 - 科學計算:天文學、基因組學等領域的計數常達到 10⁹⁰ 以上,
BigInt可避免溢位。 - 大型統計與計數:網站訪問量、社交平台粉絲數等,未來可能突破
Number上限。 - 時間序列:毫秒或納秒級時間戳累加,跨越多年仍保持正確。
在上述情境中,將資料模型設計為 BigInt(或字串+BigInt),能確保系統在面對極端數值時不會出現不預期的錯誤或精度損失。
總結
BigInt 為 JavaScript 提供了 任意精度的整數 支援,解決了 Number 在大數運算上的限制。透過字面量 n 或 BigInt() 建構子,我們可以輕鬆建立、比較與運算巨大的整數;同時,了解 型別相容性、JSON 序列化與性能成本,是安全使用 BigInt 的關鍵。
在金融、加密、科學與大數統計等領域,BigInt 已成為不可或缺的工具。只要遵循「明確型別、避免混用、適時轉型」的最佳實踐,開發者即可在日常 JavaScript 專案中,穩定且高效地處理任何大小的整數。
實務建議:在新專案的資料模型設計階段,就先評估是否需要
BigInt,若確定會處理超過安全範圍的整數,立即採用BigInt(或字串+BigInt)作為底層儲存型別,避免未來因精度問題而重構程式碼。
祝你在 JavaScript 的世界裡,玩轉大數無阻! 🚀