各位靓仔靓女,欢迎来到今天的JavaScript内核与高级编程小课堂。今天咱们聊点刺激的,聊聊JavaScript里的“巨无霸”——BigInt
,还有它背后的故事。准备好了吗?Let’s roll!
开场白:JavaScript的“整数焦虑症”
在JavaScript的世界里,数字可不是随便玩的。长期以来,JavaScript对于整数的处理,就像一个穿着紧身衣的胖子,总感觉哪里不舒服。为什么这么说呢?因为JavaScript的Number
类型,实际上是基于IEEE 754标准的双精度浮点数,它能表示的整数范围是有限的,大概就是-253 到 253 – 1。
超出这个范围会发生什么?嘿嘿,超出范围的整数,精度就丢失了,直接导致一些奇奇怪怪的问题。比如:
console.log(9007199254740991 + 1); // 9007199254740992 (没毛病)
console.log(9007199254740991 + 2); // 9007199254740992 (WTF?)
看到了吗?加个1没事,加个2就懵逼了。这就像你银行卡余额显示9999999999,但实际上你只有9999999998块钱,这还得了?
这种“整数焦虑症”在处理一些需要高精度整数的场景下,简直就是噩梦。比如:
- 金融计算:涉及到大量资金计算,精度丢失可能导致巨额损失。
- 密码学:加密算法通常需要处理非常大的整数。
- 科学计算:一些科学计算需要高精度整数。
- ID生成:如果使用自增ID,在高并发下容易超出
Number
的安全范围。
为了解决这个问题,ES2020 引入了 BigInt
,它就像一个“整数界”的超级英雄,专门用来处理任意精度的整数。
BigInt:整数界的超级英雄登场
BigInt
是一种新的原始数据类型,用于表示任意精度的整数。你可以把它想象成一个可以无限延伸的数轴,再也不用担心整数溢出的问题了。
1. 创建 BigInt
创建 BigInt
有两种方式:
- 在整数后面加上
n
后缀。 - 使用
BigInt()
构造函数。
const bigInt1 = 123456789012345678901234567890n;
const bigInt2 = BigInt(123456789012345678901234567890);
const bigInt3 = BigInt("123456789012345678901234567890"); // 从字符串创建
console.log(bigInt1); // 123456789012345678901234567890n
console.log(bigInt2); // 123456789012345678901234567890n
console.log(bigInt3); // 123456789012345678901234567890n
// 注意: 不要直接用浮点数创建BigInt
// BigInt(1.5); // Uncaught RangeError: The number 1.5 cannot be converted to a BigInt because it is not an integer
注意: 从字符串创建 BigInt
是没问题的,但是直接从浮点数创建 BigInt
是会报错的。因为 BigInt
只能表示整数。
2. BigInt 的运算
BigInt
支持大多数常见的算术运算符,比如 +
, -
, *
, /
, %
, **
。但是,BigInt
和 Number
之间的运算需要特别小心。
const bigIntA = 123456789012345678901234567890n;
const bigIntB = 98765432109876543210987654321n;
console.log(bigIntA + bigIntB); // 1333333333333333333333333333311n
console.log(bigIntA * bigIntB); // 1219326311126352690277777777777777777777777777777790n
console.log(bigIntA / 2n); // 61728394506172839450617283945n (结果会被截断为整数)
console.log(bigIntA % 3n); // 0n
// BigInt 和 Number 之间的运算会报错
// console.log(bigIntA + 1); // Uncaught TypeError: Cannot mix BigInt and other types, use explicit conversions
// 需要显式转换
console.log(bigIntA + BigInt(1)); // 123456789012345678901234567891n
console.log(Number(bigIntA) + 1); // 123456789012345678890000000000 (精度丢失!)
划重点:
BigInt
之间的运算没问题,但是和Number
混用,会抛出TypeError
错误。- 如果需要和
Number
进行运算,需要显式地将Number
转换为BigInt
,或者将BigInt
转换为Number
。 - 但是,将
BigInt
转换为Number
可能会导致精度丢失,所以要慎重使用!
3. BigInt 的比较
BigInt
可以使用标准的比较运算符 (==
, !=
, >
, <
, >=
, <=
) 进行比较。
const bigIntA = 123456789012345678901234567890n;
const bigIntB = 98765432109876543210987654321n;
console.log(bigIntA > bigIntB); // true
console.log(bigIntA < bigIntB); // false
console.log(bigIntA == bigIntB); // false
console.log(bigIntA != bigIntB); // true
console.log(bigIntA == 123456789012345678901234567890); // true (类型转换,可能会丢失精度)
console.log(bigIntA === 123456789012345678901234567890); // false (类型不同)
注意:
BigInt
和Number
之间可以使用==
进行比较,JavaScript 会进行类型转换,但是可能会丢失精度。- 为了避免类型转换带来的问题,建议使用
===
进行严格相等比较。
4. BigInt 的位运算
BigInt
支持位运算,比如 &
, |
, ^
, ~
, <<
, >>
。
const bigIntA = 10n; // 1010
const bigIntB = 3n; // 0011
console.log(bigIntA & bigIntB); // 2n (0010)
console.log(bigIntA | bigIntB); // 11n (1011)
console.log(bigIntA ^ bigIntB); // 9n (1001)
console.log(~bigIntA); // -11n (按位取反,结果是补码)
console.log(bigIntA << 1n); // 20n (左移一位)
console.log(bigIntA >> 1n); // 5n (右移一位)
5. BigInt 的一些坑
虽然 BigInt
功能强大,但是也有一些需要注意的地方:
- 不能和
Math
对象一起使用。Math
对象的方法只支持Number
类型,不能直接用于BigInt
。如果你想对BigInt
进行一些复杂的数学运算,需要自己实现。 - 除法会向下取整。
BigInt
的除法运算会直接舍弃小数部分,不会四舍五入。 - JSON 序列化问题。 默认情况下,
JSON.stringify()
不支持BigInt
类型,会抛出TypeError
错误。需要自己进行处理,比如将BigInt
转换为字符串。
const bigIntA = 123456789012345678901234567890n;
// Math.sqrt(bigIntA); // Uncaught TypeError: Cannot convert a BigInt value to a number
console.log(bigIntA / 3n); // 41152263004115226300411522630n (不是 41152263004115226300411522630.666...)
// JSON 序列化
// JSON.stringify({ value: bigIntA }); // Uncaught TypeError: Do not know how to serialize a BigInt
JSON.stringify({ value: bigIntA.toString() }); // "{"value":"123456789012345678901234567890"}"
BigInt 的底层实现(稍微深入一点点)
BigInt
的底层实现并没有一个统一的标准,不同的 JavaScript 引擎可能会有不同的实现方式。但是,一般来说,BigInt
的实现会涉及到以下几个方面:
- 数据结构: 如何存储任意精度的整数?
- 算法: 如何进行算术运算和比较运算?
- 内存管理: 如何有效地管理内存?
1. 数据结构
常见的 BigInt
数据结构有两种:
- 数组表示: 将
BigInt
拆分成多个小整数,存储在数组中。每个小整数可以是一个 32 位的整数,或者 64 位的整数。 - 链表表示: 将
BigInt
拆分成多个小整数,存储在链表中。
数组表示的优点是访问速度快,缺点是内存占用可能会比较大。链表表示的优点是内存占用比较小,缺点是访问速度比较慢。
2. 算法
BigInt
的算术运算和比较运算需要使用特殊的算法,比如:
- 加法: 可以使用类似于手算加法的方法,逐位相加,并处理进位。
- 减法: 可以使用类似于手算减法的方法,逐位相减,并处理借位。
- 乘法: 可以使用 Karatsuba 算法或者 Toom-Cook 算法等高效的乘法算法。
- 除法: 可以使用 Knuth 的除法算法。
- 比较: 可以逐位比较,从最高位开始比较。
这些算法的复杂度通常比 Number
类型的运算要高,所以 BigInt
的运算速度通常比 Number
类型的运算要慢。
3. 内存管理
BigInt
需要动态地分配内存来存储任意精度的整数,所以内存管理非常重要。JavaScript 引擎通常会使用垃圾回收机制来管理 BigInt
的内存。
BigInt 的应用场景
BigInt
解决了 JavaScript 的“整数焦虑症”,让 JavaScript 可以在更多场景下处理高精度整数。
1. 金融计算
金融计算对精度要求非常高,BigInt
可以用来处理大额资金的计算,避免精度丢失导致的损失。
const initialBalance = 1000000000000000000n; // 1000 万亿
const interestRate = 0.00000001n; // 0.000001%
const years = 10;
let balance = initialBalance;
for (let i = 0; i < years; i++) {
balance += balance * interestRate;
}
console.log(balance); // 1000000000100000000n (精确计算后的余额)
2. 密码学
密码学中需要处理非常大的整数,BigInt
可以用来实现各种加密算法。
// 一个简单的 RSA 加密示例 (简化版)
function generateKeyPair(p, q) {
const n = p * q;
const phi = (p - 1n) * (q - 1n);
const e = 65537n; // Commonly used public exponent
// 计算 d,满足 (e * d) % phi === 1n
let d = 2n;
while ((e * d) % phi !== 1n) {
d++;
}
return {
publicKey: { n: n, e: e },
privateKey: { n: n, d: d }
};
}
function encrypt(message, publicKey) {
const { n, e } = publicKey;
return power(message, e, n);
}
function decrypt(ciphertext, privateKey) {
const { n, d } = privateKey;
return power(ciphertext, d, n);
}
// 模幂运算 (Modular Exponentiation)
function power(base, exponent, modulus) {
let result = 1n;
base = base % modulus;
while (exponent > 0n) {
if (exponent % 2n === 1n) {
result = (result * base) % modulus;
}
base = (base * base) % modulus;
exponent = exponent >> 1n; // Right shift is equivalent to dividing by 2
}
return result;
}
// Example usage
const p = 61n; //For demonstration only, use much larger primes in real applications
const q = 53n;
const keyPair = generateKeyPair(p, q);
const message = 42n;
const ciphertext = encrypt(message, keyPair.publicKey);
const decryptedMessage = decrypt(ciphertext, keyPair.privateKey);
console.log("Original Message:", message);
console.log("Ciphertext:", ciphertext);
console.log("Decrypted Message:", decryptedMessage);
注意: 上面的 RSA 示例只是一个简化版,实际应用中需要使用更复杂的算法和更大的素数。
3. 科学计算
一些科学计算需要高精度整数,BigInt
可以用来进行这些计算。
// 计算阶乘
function factorial(n) {
if (n === 0n) {
return 1n;
} else {
return n * factorial(n - 1n);
}
}
console.log(factorial(20n)); // 2432902008176640000n
4. ID生成
在高并发场景下,使用自增 ID 可能会超出 Number
的安全范围,BigInt
可以用来生成唯一的 ID。
let idCounter = 0n;
function generateId() {
idCounter++;
return idCounter;
}
console.log(generateId()); // 1n
console.log(generateId()); // 2n
console.log(generateId()); // 3n
// ... 可以一直生成下去,不用担心溢出
BigInt 的兼容性
BigInt
是 ES2020 引入的新特性,所以需要考虑兼容性问题。
浏览器 | 支持版本 |
---|---|
Chrome | 67+ |
Firefox | 68+ |
Safari | 14.1+ |
Edge | 79+ |
Node.js | 10.4+ |
如果需要在不支持 BigInt
的环境中使用 BigInt
,可以使用一些 polyfill 库,比如 big-integer
。
总结:BigInt,未来的基石
BigInt
的出现,弥补了 JavaScript 在处理高精度整数方面的不足,为 JavaScript 打开了更多的可能性。虽然 BigInt
的运算速度可能比 Number
慢,但是对于那些需要高精度的场景来说,BigInt
是一个非常好的选择。
未来,随着 JavaScript 的不断发展,BigInt
将会发挥越来越重要的作用,成为 JavaScript 的一个重要基石。
结尾:课后小作业
- 尝试使用
BigInt
实现一个大数加法器。 - 研究一下
big-integer
库的实现原理。 - 思考一下
BigInt
还有哪些应用场景?
好了,今天的课就上到这里,各位靓仔靓女,下课!记得交作业哦!