初次写博文翻译,如翻译有问题请及时联系。

BigInt 数据类型旨在使JavaScript程序员能够表示大于 Number 数据类型支持的范围的整数值。当对大整数数据演示数学运算表达式的时候,能够表达任意精度的整数显得特别重要。有了 BigInt 数据类型,整数溢出再也不是问题了。

除此之外,你可以安全地使用高分辨率时间戳、大整数ID等等,而且无需使用代替方案。 BigInt 现在处于(ECMAScript标准化过程中的)第三阶段中。一旦被添到规范时, BigInt 将成为JavaScript的第二种数值数据类型,这将让Javascript支持的数据类型达到8种:

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

在本文中,我们对 BigInt 一探究竟,并了解它如何帮助克服Javascript Number 类型的限制。

问题所在

JavaScript缺少显式的整数类型常常让使用其他编程语言的程序员在使用Javascript时摸不着头脑。很多编程语言都支持多种数值类型,比如float、double、integer和bignum,但JavaScript并非如此。在JavaScript中,所有数字都是以IEEE 754-2008标准定义的双精度64位浮点格式表示。

在这个标准下,很多无法准确表示的非常大的整数都自动四舍五入了。准确地讲,在Javascript中,仅在-9007199254740991 (-(2-1)) 到 9007199254740991 (2-1)之间的Number类型可以安全地表示。不在此范围之内的整数值都可能失去原本的精度。

下面的代码可以很容易地验证这个问题:

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

上面的整数大于Javascript可以使用 Number 原语可靠地表示的最大数字。所以,它是四舍五入的。意料之外地舍入操作会让一个程序项目的可靠性和安全性大打折扣。这里又是另外一个例子:

  1. // notice the last digits
  2. 9007199254740992 === 9007199254740993; // → true

Javascript提供了允许你快速地获取到在Javascript中最大安全整数值的 Number.MAX_SAFE_INTEGE 。同样地,你可以通过使用 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

解决方案

为了解决这些限制,一些Javascript开发者使用 String 类型表示大整数。 比如,Twitter API使用JSON响应时,将ID的字符串版本添加到数据对象上。除此之外,很多现有的JS库像bignumber.js都让使用大整数更加容易。
有了 BigInt ,应用程序再也不需要额外的解决方案或库就能安全地表示在 Number.MAX_SAFE_INTEGERNumber.MIN_SAFE_INTEGER 之外的整数。现在再也不用冒着损失精度的风险,可以在标准Javascript中执行对大整数的算术运算。使用一种原生数据类型比使用第三方库有着更好的运行时性能。
创建一个 BigInt ,只需要添加一个 n 在整数末尾即可,
对比:

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

或者,你可以使用BigInt()构造函数:

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

BigInt也可以使用二进制、八进制、十六进制字面值表示:

  1. // binary
  2. console.log(0b100000000000000000000000000000000000000000000000000011n);
  3. // → 9007199254740995n
  4. // hex
  5. console.log(0x20000000000003n);
  6. // → 9007199254740995n
  7. // octal
  8. console.log(0o400000000000000003n);
  9. // → 9007199254740995n
  10. // note that legacy octal syntax is not supported
  11. console.log(0400000000000000003n);
  12. // → 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. let x = 10n;
  10. ++x; // → 11n
  11. --x; // → 10n

单目(+)运算符之所以不支持是因为一些程序(asm.js)可能依赖单目+操作总是返回一个 Number 或者抛出异常。修改+的运算行为会破坏asm.js的代码。
当然,在使用 BigInt 操作数时,算术操作符应返回BigInt类型值。因此,除(/)运算符的结果将自动舍入为最接近的整数。例如:

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

隐式类型转换

因为隐式类型转换会丢失精度,不允许 BigIntNumber 类型混合运算操作。当大整数和浮点数混合运算时,其结果可能无法用 BigInt 或者 Number 表示。注意下面的例子:

  1. (9007199254740992n + 1n) + 0.5

这个表达式的运行结果都超出了 BigIntNumber 的范围。带小数的 Number 类型数字不能转为 BigInt 类型。一个比2还要大的 BigInt 类型的数字不能被准确地转为 Number 类型。

因为这个限制,我们不能同时使用 NumberBigInt 操作数进行算术运算。你也不能将 BigInt 传递给参数是 Number 类型的Web APIs和内置Javascript函数。尝试这样做的话将造成 TypeError

  1. 10 + 10n; // → TypeError
  2. Math.max(2n, 4n, 6n); // → TypeError

注意关系运算符不用遵守这个规则,比如:

  1. 10n > 5; // → true

如果你想使用 BigIntNumber 运行算术运算,首先需要确定应在其中操作的范围。为此,通过调用 Number()BigInt() 即可转换两个操作数:

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

Boolean 上下文时, BigInt 的表现和 Number 相似。换句话说除了 0n ,一个 BigInt 总是被处理为true:

  1. if (5n) {
  2. // this code block will be executed
  3. }
  4. if (0n) {
  5. // but this code block won't
  6. }

当排序一个混有 BigIntNumber 的数组时并不会发生隐式转换:

  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 实例,如果可能的话:

  1. BigInt("10"); // → 10n
  2. BigInt(10); // → 10n
  3. BigInt(true); // → 1n

对于不能转换成 BigInt 的数据类型和传入值将会抛出一个异常:

  1. BigInt(10.2); // → RangeError
  2. BigInt(null); // → TypeError
  3. BigInt("abc"); // → SyntaxError

你可以直接由使用 BigInt 构造函数生成的实例运行算术运算:

  1. BigInt(10) * 10n; // → 100n

当用作全等运算符的操作数时,使用构造函数创建的BigInt实例的处理与其他常规的数据类型相似:

  1. BigInt(true) === 1n; // → true

方法

Javascript提供了两个方法,用于将 BigInt 数值表示为有符号或者无符号整数:

  • BigInt.asUintN() : 将BigInt的值转为0与 2-1 之间的无符号整数
  • BigInt.asIntN() : 将 BigInt 的值转为-2 与 2- 1之间的有符号整数

当运行64位算术操作时这些方法将非常有用。这样,你可以保持在预期范围内。

浏览器支持与转码

当写到这里的时候,Chrome+67 和Opera +54 已经完全支持 BigInt 数据类型了。不幸的的是,Edge 和Safari 尚未实施。Firefox 默认不支持 BigInt ,但通过在 about: config 设置 javascript.options.biginttrue 就能使用BigInt了。可以在 Can I use…网站中找到已支持这项功能的最新浏览器列表。
不幸的是,转译BigInt是一个极其复杂的过程,这个过程会导致大量的运行时性能损耗。直接对 BigInt 进行polyfill也是不可能的,这个方案会影响现有的几个操作符。目前有一个比较好的选择是使用一个纯JS编写的实现BigInt的JSBI库

这个库提供了一个类似 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 plugin插件自动地编译爹JSBI代码为 BigInt 代码。此外,JSBI的性能与原生 BigInt 的性能相当。你可以期待更多的浏览器支持BigInt。

结论

BigInt 是一个用于整数值超出 Number 数据类型支持的范围之外的新数据结构。这个数据类型允许我们安全地进行行大整数的算术操作、表示高分辨率时间戳、使用大整数ID而不需要使用第三方库。
需要格外注意的是你不能把 Number 数据类型和 BigInt 数据类型的操作数进行混合算术运算。你需要通过显式转换任何一个操作数来确定应在其中进行操作的域。还有为了兼容性,你不能在 BigInt 使用单目(+)操作操作符。

你是怎么想的呢?你觉得 BigInt 很有用吗?评论见。

原文地址:https://www.smashingmagazine.com/2019/07/essential-guide-javascript-newest-data-type-bigint/ 原文作者:Faraz Kelhini 翻译参考: 《JavaScript高级程序设计》 https://translate.google.cn/
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/BigInt