各位观众,早上好/下午好/晚上好!我是今天的主讲人,咱们今天的主题是:JavaScript的BigInt和Number,以及它们在JavaScript引擎盖下面的那些事儿,还有类型转换时的一些“爱恨情仇”。准备好,我们要开车了!
第一站:数字的“前世今生”——Number类型
在JavaScript的世界里,Number可不是一个简简单单的整数。它可是一个“全能选手”,既能代表整数,也能代表浮点数,甚至还能代表一些特殊的值,比如Infinity(无穷大)、-Infinity(负无穷大)和NaN(Not a Number,不是一个数字)。
咱们先来看看Number在JavaScript引擎里是怎么“安家落户”的。Number采用的是IEEE 754双精度浮点数格式。这意味着什么呢?这意味着它用64位来存储一个数字,这64位又被分成三部分:
- 符号位(Sign): 1位,用来表示正负号(0表示正数,1表示负数)。
- 指数位(Exponent): 11位,用来表示指数。
- 尾数位(Mantissa/Significand): 52位,用来表示有效数字。
这个表示方法决定了Number能表示的范围和精度。
- 能表示的最大安全整数:
Number.MAX_SAFE_INTEGER,值为 9007199254740991 (2^53 – 1)。 - 能表示的最小安全整数:
Number.MIN_SAFE_INTEGER,值为 -9007199254740991 (-(2^53 – 1))。
超过这个范围的整数,JavaScript就不能精确地表示了。这就好比你有一个只能装53个鸡蛋的盒子,超过53个你就只能估摸着装了,没法精确计数了。
console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991
console.log(Number.MIN_SAFE_INTEGER); // -9007199254740991
console.log(Number.MAX_VALUE); // 1.7976931348623157e+308
console.log(Number.MIN_VALUE); // 5e-324
代码小剧场:精度丢失的“惨案”
console.log(0.1 + 0.2); // 0.30000000000000004
看到了吗?0.1 + 0.2 竟然不等于 0.3! 这就是浮点数精度丢失的典型案例。这是因为0.1和0.2在二进制表示下是无限循环小数,而有限的尾数位无法精确存储无限循环小数,所以只能进行近似表示,导致计算结果出现误差。
第二站:BigInt——“超大号”整数的登场
为了解决Number在表示大整数时的精度问题,ES2020引入了BigInt。BigInt可以表示任意精度的整数,妈妈再也不用担心我的计算结果不准确了!
BigInt的底层表示方式与Number不同。它通常使用动态分配的内存来存储任意长度的整数。这使得BigInt可以表示比Number.MAX_SAFE_INTEGER更大的整数,而不会丢失精度。
创建BigInt有两种方式:
- 在整数后面加上
n:123n - 使用
BigInt()函数:BigInt(123)或BigInt("123")
const bigInt1 = 123456789012345678901234567890n;
const bigInt2 = BigInt(123456789012345678901234567890);
const bigInt3 = BigInt("123456789012345678901234567890");
console.log(bigInt1); // 123456789012345678901234567890n
console.log(bigInt2); // 123456789012345678901234567890n
console.log(bigInt3); // 123456789012345678901234567890n
注意事项:
BigInt不能和Number混合运算。你需要显式地将它们转换为同一种类型。- 不能使用
Math对象中的方法来操作BigInt。
代码小剧场:BigInt 的“独角戏”
const number = 10;
const bigInt = 20n;
// console.log(number + bigInt); // TypeError: Cannot mix BigInt and other types, use explicit conversions
console.log(number + Number(bigInt)); // 30
console.log(BigInt(number) + bigInt); // 30n
// console.log(Math.sqrt(bigInt)); // TypeError: Cannot convert a BigInt value to a number
第三站:类型转换——Number 和 BigInt 的“恩怨情仇”
类型转换是JavaScript中一个非常重要的概念。在进行运算或者比较时,JavaScript会根据需要自动进行类型转换。Number和BigInt之间的类型转换也需要我们特别关注。
1. Number 转 BigInt:
可以使用 BigInt() 函数将 Number 转换为 BigInt。但是,如果 Number 超出了 Number.MAX_SAFE_INTEGER 或 Number.MIN_SAFE_INTEGER 的范围,转换后的 BigInt 可能会丢失精度。
const num = 9007199254740992; // 超过了 MAX_SAFE_INTEGER
const bigInt = BigInt(num);
console.log(bigInt); // 9007199254740992n (看起来一样,但实际上精度已经丢失)
console.log(num === Number(bigInt)); // true (虽然精度丢失,但是转换回来值相等)
const num2 = 9007199254740991; // 未超过 MAX_SAFE_INTEGER
const bigInt2 = BigInt(num2);
console.log(bigInt2); // 9007199254740991n
console.log(num2 === Number(bigInt2)); // true (精度未丢失)
2. BigInt 转 Number:
可以使用 Number() 函数将 BigInt 转换为 Number。但是,如果 BigInt 的值超出了 Number 的表示范围,转换后的结果可能是 Infinity 或 -Infinity。如果 BigInt 的值在 Number 的安全整数范围内,转换后的结果是精确的。
const bigInt1 = 9007199254740992n; // 超过了 MAX_SAFE_INTEGER
const num1 = Number(bigInt1);
console.log(num1); // 9007199254740992 (精度丢失)
const bigInt2 = 123456789012345678901234567890n; // 远远超过 MAX_SAFE_INTEGER
const num2 = Number(bigInt2);
console.log(num2); // Infinity
3. 隐式类型转换:
在某些情况下,JavaScript会尝试进行隐式类型转换。但是,BigInt 和 Number 之间的隐式类型转换通常会导致错误。所以,最好避免依赖隐式类型转换,而是显式地进行类型转换。
- 比较运算符: 比较运算符 (
==,===,!=,!==,>,<,>=,<=) 在比较Number和BigInt时,会先将Number转换为BigInt,然后再进行比较(如果Number在安全范围内)。
console.log(10 == 10n); // true (10 被转换为 10n)
console.log(10 === 10n); // false (类型不同)
console.log(11 > 10n); // true (11 被转换为 11n)
- 逻辑运算符: 逻辑运算符 (
&&,||,!) 会将BigInt视为 truthy 或 falsy 值。0n是 falsy,其他BigInt值都是 truthy。
console.log(Boolean(0n)); // false
console.log(Boolean(1n)); // true
console.log(Boolean(-1n)); // true
类型转换总结:
| 转换方向 | 方法 | 注意事项 |
|---|---|---|
| Number -> BigInt | BigInt(number) |
如果 Number 超出安全整数范围,转换可能丢失精度。 |
| BigInt -> Number | Number(bigInt) |
如果 BigInt 的值超出 Number 的表示范围,结果可能是 Infinity 或 -Infinity。如果在安全范围内,转换是精确的。 |
| 隐式转换 | 比较运算符、逻辑运算符 | 尽量避免依赖隐式转换,显式转换更安全。 |
第四站:实际应用场景
BigInt 在哪些场景下能够大展拳脚呢?
- 处理金融数据: 金融计算通常需要很高的精度,
BigInt可以避免精度丢失,保证计算结果的准确性。 - 密码学: 密码学算法中经常需要处理大整数,
BigInt可以满足这些需求。 - 科学计算: 某些科学计算也需要处理大整数,
BigInt可以提供支持。 - 处理超大ID: 在分布式系统中,ID可能会非常大,
BigInt可以用来表示这些ID。
代码示例:一个简单的计算器
function calculate(a, b, operator) {
if (typeof a === 'number') a = BigInt(a);
if (typeof b === 'number') b = BigInt(b);
switch (operator) {
case '+': return a + b;
case '-': return a - b;
case '*': return a * b;
case '/':
if (b === 0n) throw new Error("Division by zero");
return a / b; // 注意: BigInt 的除法会舍去小数部分
case '%':
if (b === 0n) throw new Error("Modulo by zero");
return a % b;
default: throw new Error("Invalid operator");
}
}
console.log(calculate(10, 5, '+')); // 15n
console.log(calculate(100n, 5n, '-')); // 95n
console.log(calculate(20n, 3, '*')); // 60n
console.log(calculate(100n, 3n, '/')); // 33n (舍去小数)
console.log(calculate(100n, 3n, '%')); // 1n
// console.log(calculate(100n, 0n, '/')); // Error: Division by zero
let result = calculate(Number.MAX_SAFE_INTEGER, 1, '+');
console.log(result); //9007199254740992n
第五站:性能考量
虽然 BigInt 解决了精度问题,但是它的性能通常比 Number 慢。这是因为 BigInt 的底层实现更加复杂,需要进行更多的内存分配和运算。因此,在性能敏感的场景下,需要谨慎使用 BigInt。
一般来说,如果不需要处理超出 Number 安全范围的整数,或者对性能要求非常高,那么 Number 仍然是更好的选择。只有在需要处理大整数并且精度非常重要的情况下,才应该考虑使用 BigInt。
总结
今天咱们一起了解了JavaScript的Number和BigInt类型,以及它们之间的类型转换。希望通过今天的讲解,大家能够对JavaScript的数字类型有更深入的理解,并且能够在实际开发中灵活运用。记住,选择合适的类型,才能让你的代码更加健壮、高效!
下次有机会再跟大家分享其他的技术知识,拜拜!