简介:在JavaScript中,Number类型无法安全地表示大于 2的53次方 的整数值。这个限制迫使开发人员使用低效的方法或第三方库。BigInt是一种旨在解决此问题的新数据类型。

BigInt 数据类型的目标是使 JavaScript 开发人员使用到比Number 类型支持范围更大的整数值。在运算过程中,如果有大的整数,最重要的是能够以任意精度来表示这个整数。这时候如果使用BigInt类型,就没有整数溢出的问题。

此外,你可以安全地使用高分辨率时间戳,大整数ID等,而无需再使用其他的解决方案。BigInt目前是第3阶段的提案,一旦添加到规范中,它将成为JavaScript中的第二个数字数据类型,JavaScript将支持的数据类型总数增加到八个:

在本文中,我们将仔细研究 BigInt 并了解它如何帮助解决 Number类型的限制。

问题

JavaScript中缺少显式整数类型的问题通常会让熟练其他语言的程序员感到困惑。许多语言都支持多种数字类型,例如float,double,integer和bignum,但JavaScript的情况并非如此。在JavaScript中,所有数字都以IEEE 754-2008标准定义的双精度64位浮点格式表示。

在此标准下,无法精确表示的非常大的整数会自动舍入。确切地说,Number JavaScript中的类型只能准确地表示-9007199254740991( - (2^53 - 1))和9007199254740991(2 ^53 - 1)之间的整数。超出这个范围的任何整数值都可能会失去精度。

通过执行以下代码可以验证这一点:

console.log(9999999999999999);    // → 10000000000000000

这个整数比 JavaScript可以用 Number 函数表示的最大数字还要更大。因此,它会自动舍入。异常的舍入可能会影响代码的可靠性和安全性。看看另一个例子:

// notice the last digits
9007199254740992 === 9007199254740993;    // → true

JavaScript提供了Number.MAX_SAFE_INTEGER常量,可以让你快速获取到 JavaScript 中的最大的准确的整数。同样,您可以使用Number.MIN_SAFE_INTEGER常量获取最小的整数:

const minInt = Number.MIN_SAFE_INTEGER;

console.log(minInt);         // → -9007199254740991

console.log(minInt - 5);     // → -9007199254740996

// notice how this outputs the same value as above
console.log(minInt - 4);     // → -9007199254740996

解决方案

作为解决这些限制的方案,一些开发人员使用该String类型表示那些大的整数。比如Twitter API 使用JSON响应时增加了ID的字符串的版本对象。此外,还有许多像bignumber.js 的库,可以更好地处理大的整数。

有了 BigInt,代码就不再需要一个额外的方法或库来表示超出Number.MAX_SAFE_INTEGER和的整数Number.Min_SAFE_INTEGER。现在可以在标准JavaScript中执行对这种大的整数的算术运算,而且不会有精度损失的风险。对比原始数据类型运算 跟 第三方库的运算,原始数据类型运算的运行性能要占有优势。

要创建一个BigInt,只需简单地加一个 n 到整数的末尾。比较一下:

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

或者,你也可以调用BigInt()构造函数进行创建:

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

BigInt 文字也可以用二进制、八进制或十六进制编写:

// binary
console.log(0b100000000000000000000000000000000000000000000000000011n);
// → 9007199254740995n

// hex
console.log(0x20000000000003n);
// → 9007199254740995n

// octal
console.log(0o400000000000000003n);
// → 9007199254740995n

// note that legacy octal syntax is not supported
console.log(0400000000000000003n);
// → SyntaxError

注意, 不能使用全等运算符(严格运算符)来将 BigInt 和常规数字进行比较,因为它们的类型不同:

console.log(10n === 10);    // → false

console.log(typeof 10n);    // → bigint
console.log(typeof 10);     // → number

不过,你可以使用相等运算符,这个运算符在编译会在比较之前进行隐式类型转换:

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

除了一元plus(+)运算符之外,所有算术运算符都可以在 BigInt 上使用:

10n + 20n;    // → 30n
10n - 20n;    // → -10n
+10n;         // → TypeError: Cannot convert a BigInt value to a number
-10n;         // → -10n
10n * 20n;    // → 200n
20n / 10n;    // → 2n
23n % 10n;    // → 3n
10n ** 3n;    // → 1000n

const x = 10n;
++x;          // → 11n
--x;          // → 9n

不支持一元plus(+)运算符的原因是:某些代码可能通过 + 生成一个 Number 类型或抛出异常。改变 + 的行为函数会破坏 asm.js 代码。

当然,如果与BigInt类型的值进行操作时,算术运算符应该还是返回一个BigInt值。因此,除法(/)运算符的结果会自动向下舍入到最接近的整数。例如:

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

隐式类型转换

由于隐式类型转换可能会丢失信息,因此不允许在BigIntNumber 之间进行混合操作。当有大的整数值和浮点数共同运算时,得到的结果值可能既无法用 BigInt,也无法用 Number 进行准确的表示。请考虑以下示例:

(9007199254740992n + 1n) + 0.5

此表达式的结果是 BigIntNumber 两种类型的范围之外。Number带小数部分的,不能准确地转换为 BigInt,同时 BigInt 大于2^53,也不能准确地转换为 Number

由于这种限制,不能使用 NumberBigInt 操作数进行混合执行算术运算。你也无法将 BigInt 作为参数传递给 参数只接受 Number 类型 Web API 或者的内置 JavaScript 函数。否则这样做会抛出 TypeError 异常:

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

不过呢,关系运算符不遵循这个规则,如下例所示:

10n > 5;    // → true

如果要使用BigIntNumber 进行算术计算,首先需要确定应在其中执行操作的范围。要做到这一点,只需通过调用Number()BigInt()来转换任何一个操作数:

BigInt(10) + 10n;    // → 20n
// or
10 + Number(10n);    // → 20

Boolean的上下文中,BigInt 被视为类似 Number。换句话说,一个BigInt 类型的变量,只要它不是0n,就会被认为是true值:

if (5n) {
    // this code block will be executed
}

if (0n) {
    // but this code block won't
}

数组排序的时候, BigIntNumber 不会发生隐式类型转换:

const arr = [3n, 4, 2, 1n, 0, -1n];

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

位运算符例如|&<<>>,和^上操作BigInt其实跟 Number的方式差不多。负数被解释为无限长度的二进制补码。不允许他们混合进行位运算。看以下的例子:

90 | 115;      // → 123
90n | 115n;    // → 123n
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(true) === 1n;    // → true

库的职能

JavaScript提供了两个库函数,用于将BigInt值表示为有符号或无符号整数:

执行64位算术运算时,这些函数特别有用。这样你就可以保持在预期范围内。

浏览器支持和透明

在撰写本文时,Chrome +67和Opera +54完全支持BigInt数据类型。不过,Edge和Safari还没有支持它。Firefox在默认情况下不支持BigInt,但它可以通过设置来启用javascript.options.bigint,以trueabout:config。可以从Can I Use...中查看支持浏览器的最新内容。

遗憾的是,转换为BigInt类型是一个非常复杂的过程,会严重的影响运行性能。直接填充BigInt 也行不通,因为这个提案改变了几个现有运营商的行为。目前,较好的选择是使用JSBI库,它是用纯JavaScript实现BigInt的提案。

这个库提供的行为与原生数据类型BigInt完全相同。以下是使用JSBI的方法:

import JSBI from './jsbi.mjs';

const b1 = JSBI.BigInt(Number.MAX_SAFE_INTEGER);
const b2 = JSBI.BigInt('10');

const result = JSBI.add(b1, b2);

console.log(String(result));    // → '9007199254741001'

使用JSBI的一个优点是,一旦浏览器支持得到改进,你就不需要重写代码。相反,你可以使用babel插件自动将JSBI代码编译为原生BigInt代码。此外,JSBI的性能与原生BigInt实的现差不多。你可以期待 BigInt 类型会很快能获得更多浏览器的支持。

结论

BigInt是一种新的数据类型,意指在当整数值大于Number数据类型支持的范围之外时进行使用。这个数据类型允许我们准确地对大型整数进行算术运算,能很好地表示高分辨率时间戳或者使用大整数ID等,而无需使用其他库。

要记住重要的是,不能将NumberBigInt类型的操作数混合进行算术运算。你需要通过显式转换任一操作数来确定它们在其中计算操作的范围。此外,出于兼容性原因,你不能在BigInt类型的数值上使用一元运算符 plus(+)。

最后,你怎么看?你觉得BigInt有用吗?

原文链接:《The Essential Guide To JavaScript’s Newest Data Type: BigInt》