JavaScript内核与高级编程之:`JavaScript` 的 `BigInt` 与 `Number`:其在 `JavaScript` 中的底层表示和类型转换。

各位观众,早上好/下午好/晚上好!我是今天的主讲人,咱们今天的主题是:JavaScriptBigIntNumber,以及它们在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引入了BigIntBigInt可以表示任意精度的整数,妈妈再也不用担心我的计算结果不准确了!

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

第三站:类型转换——NumberBigInt 的“恩怨情仇”

类型转换是JavaScript中一个非常重要的概念。在进行运算或者比较时,JavaScript会根据需要自动进行类型转换。NumberBigInt之间的类型转换也需要我们特别关注。

1. NumberBigInt

可以使用 BigInt() 函数将 Number 转换为 BigInt。但是,如果 Number 超出了 Number.MAX_SAFE_INTEGERNumber.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. BigIntNumber

可以使用 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会尝试进行隐式类型转换。但是,BigIntNumber 之间的隐式类型转换通常会导致错误。所以,最好避免依赖隐式类型转换,而是显式地进行类型转换。

  • 比较运算符: 比较运算符 (==, ===, !=, !==, >, <, >=, <=) 在比较 NumberBigInt 时,会先将 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

总结

今天咱们一起了解了JavaScriptNumberBigInt类型,以及它们之间的类型转换。希望通过今天的讲解,大家能够对JavaScript的数字类型有更深入的理解,并且能够在实际开发中灵活运用。记住,选择合适的类型,才能让你的代码更加健壮、高效!

下次有机会再跟大家分享其他的技术知识,拜拜!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注