各位观众,早上好/下午好/晚上好!我是今天的主讲人,咱们今天的主题是: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
的数字类型有更深入的理解,并且能够在实际开发中灵活运用。记住,选择合适的类型,才能让你的代码更加健壮、高效!
下次有机会再跟大家分享其他的技术知识,拜拜!