js中的Number数据类型是有表示上下限的
BigInt数据类型的目的是比Number数据类型支持的范围更大的整数值。在对大整数执行数学运算时,以任意精度表示整数的能力尤为重要。使用BigInt,整数溢出将不再是问题。
此外,可以安全地使用更加准确时间戳,大整数ID等,而无需使用变通方法。 BigInt目前是第3阶段提案, 一旦添加到规范中,它就是JS 第二个数字数据类型,也将是 JS 第8种基本数据类型:

  • Boolean
  • Null
  • Undefined
  • Number
  • BigInt
  • String
  • Symbol
  • Object

在本文中,咱们将详细介绍BigInt,看看它如何解决使用Number类型的限制。

问题

对于学过其他语言的程序员来说,JS中缺少显式整数类型常常令人困惑。许多编程语言支持多种数字类型,如浮点型、双精度型、整数型和双精度型,但JS却不是这样。在JS中,按照IEEE 754-2008标准的定义,所有数字都以双精度64位浮点格式表示。
在此标准下,无法精确表示的非常大的整数将自动四舍五入。确切地说,JS 中的Number类型只能安全地表示-9007199254740991 (-(2^53-1)) 和9007199254740991(2^53-1)之间的整数,任何超出此范围的整数值都可能失去精度。

  1. console.log(9999999999999999); // → 10000000000000000

该整数大于JS Number 类型所能表示的最大整数,因此,它被四舍五入的。意外四舍五入会损害程序的可靠性和安全性。这是另一个例子:

  1. // 注意最后一位的数字 9007199254740992 === 9007199254740993; // → true

JS 提供Number.MAX_SAFE_INTEGER常量来表示 最大安全整数,Number.MIN_SAFE_INTEGER常量表示最小安全整数:

  1. const minInt = Number.MIN_SAFE_INTEGER;
  2. console.log(minInt); // → -9007199254740991
  3. console.log(minInt - 5); // → -9007199254740996
  4. // notice how this outputs the same value as above
  5. console.log(minInt - 4); // → -9007199254740996

解决方案

为了解决这些限制,一些JS开发人员使用字符串类型表示大整数。 例如,Twitter API 在使用 JSON 进行响应时会向对象添加字符串版本的 ID。 此外,还开发了许多库,例如 bignumber.js,以便更容易地处理大整数。
使用BigInt,应用程序不再需要变通方法或库来安全地表示Number.MAX_SAFE_INTEGER和Number.Min_SAFE_INTEGER之外的整数。 现在可以在标准JS中执行对大整数的算术运算,而不会有精度损失的风险。
要创建BigInt,只需在整数的末尾追加n即可。比较:

  1. console.log(9007199254740995n); // → 9007199254740995n
  2. console.log(9007199254740995); // → 9007199254740996

或者,可以调用BigInt()构造函数

  1. BigInt("9007199254740995"); // → 9007199254740995n

BigInt文字也可以用二进制、八进制或十六进制表示

  1. // binary 二进制
  2. console.log(0b100000000000000000000000000000000000000000000000000011n); // → 9007199254740995n
  3. // hex 十六进制
  4. console.log(0x20000000000003n); // → 9007199254740995n
  5. // octal 八进制
  6. console.log(0o400000000000000003n); // → 9007199254740995n
  7. // note that legacy octal syntax is not supported
  8. console.log(0400000000000000003n); // → SyntaxError

请记住,不能使用严格相等运算符将BigInt与常规数字进行比较,因为它们的类型不同:

  1. console.log(10n === 10); // → false
  2. console.log(typeof 10n); // → bigint
  3. console.log(typeof 10); // → number

相反,可以使用等号运算符,它在处理操作数之前执行隐式类型转换

  1. console.log(10n == 10); // → true

除一元加号(+)运算符外,所有算术运算符都可用于BigInt

  1. 10n + 20n; // → 30n
  2. 10n - 20n; // → -10n
  3. +10n; // → TypeError: Cannot convert a BigInt value to a number
  4. -10n; // → -10n
  5. 10n * 20n; // → 200n
  6. 20n / 10n; // → 2n
  7. 23n % 10n; // → 3n
  8. 10n ** 3n; // → 1000n
  9. const x = 10n;
  10. ++x; // → 11n
  11. --x; // → 9n

不支持一元加号(+)运算符的原因是某些程序可能依赖于+始终生成Number的不变量,或者抛出异常。 更改+的行为也会破坏asm.js代码。
当然,与BigInt操作数一起使用时,算术运算符应该返回BigInt值。因此,除法(/)运算符的结果会自动向下舍入到最接近的整数。例如:

  1. 25 / 10; // → 2.5
  2. 25n / 10n; // → 2n

隐式类型转换

  1. 因为隐式类型转换可能丢失信息,所以不允许在bigint Number 之间进行混合操作。当混合使用大整数和浮点数时,结果值可能无法由BigIntNumber精确表示。思考下面的例子:<br />(9007199254740992n + 1n) + 0.5 <br /> 这个表达式的结果超出了BigIntNumber的范围。小数部分的Number不能精确地转换为BigInt。大于2^53BigInt不能准确地转换为数字。<br /> 由于这个限制,不可能对混合使用NumberBigInt操作数执行算术操作。还不能将BigInt传递给Web api和内置的 JS 函数,这些函数需要一个 Number 类型的数字。尝试这样做会报TypeError错误
  1. 10 + 10n; // → TypeError
  2. Math.max(2n, 4n, 6n); // → TypeError

请注意,BigInt和Number可以进行关系运算,如下例所示:

  1. 10n > 5; // → true

如果希望使用BigInt和Number执行算术计算,首先需要确定应该在哪个类型中执行该操作。为此,只需通过调用Number()或BigInt()来转换操作数:

  1. BigInt(10) + 10n; // → 20n
  2. // or
  3. 10 + Number(10n); // → 20

当 Boolean 类型与BigInt 类型相遇时,BigInt的处理方式与Number类似,换句话说,只要不是0n,BigInt就被视为truthy的值:

  1. if (5n) { // 这里代码块将被执行 }
  2. if (0n) { // 这里代码块不会执行 }

BigInts和Numbers可以放在同一个数组中
排序BigInts和Numbers数组时,不会发生隐式类型转换:

  1. const arr = [3n, 4, 2, 1n, 0, -1n];
  2. arr.sort(); // → [-1n, 0, 1n, 2, 3n, 4]

位操作符如|、&、<<、>>和^对Bigint的操作方式与Number类似。下面是一些例子

  1. 90 | 115; // → 123
  2. 90n | 115n; // → 123n
  3. 90n | 115; // → TypeError

BigInt构造函数

与其他基本类型一样,可以使用构造函数创建BigInt。传递给BigInt()的参数将自动转换为BigInt:
BigInt(“10”); // → 10n BigInt(10); // → 10n BigInt(true); // → 1n
无法转换的数据类型和值会引发异常:
BigInt(10.2); // → RangeError BigInt(null); // → TypeError BigInt(“abc”); // → SyntaxError
可以直接对使用构造函数创建的BigInt执行算术操作
BigInt(10) * 10n; // → 100n
使用严格相等运算符的操作数时,使用构造函数创建的Bigint与常规Bigint的处理方式类似
BigInt(true) === 1n; // → true

库函数

在撰写本文时,BigInt已经进入第4代,之前不支持BigInt的Edge和Safari和火狐也已经支持了,所以BigInt已经开始逐渐步入正轨了。

不幸的是,转换BigInt是一个极其复杂的过程,这会导致严重的运行时性能损失。直接polyfill BigInt也是不可能的,因为该提议改变了几个现有操作符的行为。目前,更好的选择是使用JSBI库,它是BigInt提案的纯JS实现。
这个库提供了一个与原生BigInt行为完全相同的API。下面是如何使用JSBI:

  1. import JSBI from './jsbi.mjs';
  2. const b1 = JSBI.BigInt(Number.MAX_SAFE_INTEGER);
  3. const b2 = JSBI.BigInt('10');
  4. const result = JSBI.add(b1, b2);
  5. console.log(String(result)); // → '9007199254741001'

使用JSBI的一个优点是,一旦浏览器支持,就不需要重写代码。 相反,可以使用babel插件自动将JSBI代码编译为原生 BigInt代码。

总结

BigInt是一种新的数据类型,用于当整数值大于Number数据类型支持的范围时。这种数据类型允许我们安全地对大整数执行算术操作,表示高分辨率的时间戳,使用大整数id,等等,而不需要使用库。
重要的是要记住,不能使用Number和BigInt操作数的混合执行算术运算,需要通过显式转换其中的一种类型。 此外,出于兼容性原因,不允许在BigInt上使用一元加号(+)运算符。

静态方法

BigInt.asIntN()
将 BigInt 值转换为一个 -2width-1 与 2width-1-1 之间的有符号整数。
BigInt.asUintN()
将一个 BigInt 值转换为 0 与 2width-1 之间的无符号整数。

实例方法

BigInt.prototype.toLocaleString()
返回此数字的 language-sensitive 形式的字符串。覆盖 Object.prototype.toLocaleString() 方法。
BigInt.prototype.toString()
返回以指定基数(base)表示指定数字的字符串。覆盖 Object.prototype.toString() 方法。
BigInt.prototype.valueOf()
返回指定对象的基元值。 覆盖 Object.prototype.valueOf() 方法。

使用建议

转化

由于在 Number 与 BigInt 之间进行转换会损失精度,因而建议仅在值可能大于253 时使用 BigInt 类型,并且不在两种类型之间进行相互转换。

密码学

由于对 BigInt 的操作不是常数时间的,因而 BigInt 不适合用于密码学
对于密码学中的RSA算法,RSA使用的是幂运算加密,所以涉及到的数字一般都特别大,远远超出number能表示的返回,所以对于这一点我觉得只能使用BigInt类型来表示。

在 JSON 中使用

对任何 BigInt 值使用 JSON.stringify() 都会引发 TypeError,因为默认情况下 BigInt 值不会在 JSON 中序列化。但是,如果需要,可以实现 toJSON 方法:

  1. BigInt.prototype.toJSON = function() { return this.toString(); }

JSON.stringify 现在生成如下字符串,而不是抛出异常:

  1. JSON.stringify(BigInt(1));
  2. // '"1"'