JavaScript BigInt 使用指南

我正在参加「掘金·启航计划」

JavaScript BigInt 使用指南

我们都知道 JavaScript 是一门不断发展的语言。在 ES2020 的 2020 版发布之前,没有原生方法可以准确表示和执行大于 9,007,199,254,740,9912**53 – 1 或小于 -9,007,199,254,740,991-(2**53 – 1) 的数字的数学运算。大多数开发人员依赖 JSBI 和 bignumber.js 等库来对非常大的数字执行计算。

这种情况产生了另一种数据类型,称为 BigIntBigInt 的引入使 JavaScript 数据类型总数达到了八种。在本文中,我们将了解 BigInt 是什么、它的优点以及如何在 JavaScript 中正确使用它。要了解有关在 Node.js 应用程序中表示大数的更多信息,请查看 如何在 Node.js 应用程序中处理大数

什么是 BigInt?

BigInt 是一种数值数据类型,可用于表示旧数值数据类型 number 无法表示的小数和大数。每个 BigInt 值都必须在数字后包含小写字母 n,例如 897n。因此,说 21 不严格等于 21n 是准确的,因为前者是一个 number,而后者是一个 bigint

当你对任何大于 9,007,199,254,740,991Number.MAX_SAFE_INTEGER 的数字执行数学运算时,你实际上并没有得到准确的答案。我们可以看到下面的示例:

const largeNumber = Number.MAX_SAFE_INTEGER; // or 9007199254740991






const addOne = largeNumber + 1;
console.log(addOne);
// returns 9007199254740992





const addTwo = largeNumber + 2;
console.log(addTwo);
// Also returns 9007199254740992
// Should return 9007199254740993

const addSix = largeNumber + 6;
console.log(addSix);
// returns 9007199254740996
// Should return 9007199254740997



const bb = 12n;

console.log(bb);

但是,我们可以使用 BigInt 来准确地处理这些类型的操作。要将整数定义为 BigInt,我们可以执行以下任一方法:我们可以将 n 附加到整数或对整个数值或仅包含整数值的字符串调用 BigInt() 构造函数。例如下面的示例:

// Method 1
const sampleBigIntOne = 234n;









// Method 2
const sampleBigIntTwo = BigInt(567)
// returns 567n






const sampleBigIntThree = BigInt("123")
// returns 123n



const sampleBigIntFour = 12356789900099999999912111n
// 仍然正确

要验证一个值是 BigInt 类型,我们可以像这样使用 typeof 运算符:

const sampleOne = 17n
console.log(typeof sampleOne)
// Expected result: "bigint" 




const sampleTwo = BigInt(789);
console.log(typeof sampleTwo)
// Expected result: "bigint"
// 这是一个不推荐的写法


const sampleThree = typeof 17n === "bigint"
console.log(sampleThree)
// Expected result: true

注意:在 BigInt 构造函数中传递数字是一种不推荐的写法法。相反,最好只附加 n 或首先将其包装在一个字符串中,分别如 sampleBigIntOnesampleBigIntThree 中那样。

BigInt 的使用示例

BigInt 值在大数至为重要的生活或计算领域得到了应用。以下是 BigInt 的一些关键用例:

  • 财务计算:BigInt 在财务计算中很重要,因为可以处理非常大量的交易和货币转换
  • 密码学和安全计算:BigInt 在密码学中用于生成非常难以预测或破解的非常大的随机数
  • 游戏开发:在游戏开发中,大数通常用于保存时间戳、分数和跟踪进度。 BigInt 的使用确保了此类值的准确表示
  • 分布式系统:分布式系统需要唯一身份才能准确执行。由于 BigInt 值是无限的,因此它们可用于生成标识符和密钥

BigInt 在这些领域确实很重要,因为它让开发人员安全、精确地处理巨大的整数、时间戳、ID 和进程。

BigInt vs Number

通常,JavaScript 中的数字可以用来表示整数,甚至可以表示浮点数,比如 694.125677。它包括介于 9,007,199,254,740,9912**53 – 1-9,007,199,254,740,991-(2**53 – 1) 之间的值。这些限制可用作常量,例如分别为 Number.MAX_SAFE_INTEGERNumber.MIN_SAFE_INTEGER

实际上,你可以对数字进行所有算术运算,例如加法 (+)、减法 (-)、乘法 (*)、除法 (/)、取余取模 (%) 和指数 (**)在该范围内没有任何问题,但 BigInt 并非完全如此。

BigInt 可用于表示和执行任何大小的整数运算,浮点数或小数除外,包括大于 9,007,199,254,740,991 或小于 -9,007,199,254,740,991 的数字。例如 17.2n 这样的十进制值是无效的。

通常,在两个或多个 BigInt 值之间使用时, (/) 之外的所有算术运算都会给出正确的数学答案。除法运算符不会始终给出准确的结果,所以 5n/2n 等运算将舍弃小数部分2n 而不是 2.5n

Number 类型的限制

JavaScript 数字类型被写成双精度 64 位二进制格式 IEEE 754 值,52 位用于有效值,而 1 位和 11 位用于符号和指数。因此,无论何时使用 Number,我们都可能面临一些限制。这些限制包括:

  • 舍入误差和数字精度有限
  • 无法在最小和最大安全整数之外执行精确运算
  • 无法执行超出 MAX_VALUEMIN_VALUE 范围的操作

JavaScript 必然会对数字进行四舍五入,因为它使用 64 位来表示浮点数。这意味着 JavaScript 中的每个数字在存储到内存中之前都会先转换为双精度二进制浮点数。而数字的 64 位表示实际上分为三部分,包括有效数字偏移指数符号

例如,像 15 这样的数字转换后如下所示: 0.10000000010.1110000000000000000000000000000000000000000000000000

双精度二进制浮点数的第一部分是符号,可以是 0 或 1,代表数字的正负号。因此,符号为 0 时为正,反之亦然。占用 1 位。第二部分是偏移指数,占用 11 位,最后一部分是有效数字或尾数,占用 52 位。

有些数字给出精确值,例如 1/21/41/51/81/10 以及 9,007,199,254,740,9912**53 – 1-9,007,199,254,740,991-(2**53 – 1)。其他一些数字没有给出确切的值;相反,它们是重复小数,例如 1/31/61/71/9

问题在于,其中一些十进制数在转换为二进制时会失去精度,反之亦然。因此,当你取一个小数(例如 0.1)并将其加起来为 0.2 时,它们加起来不会等于 0.3。相反,它们加起来为 0.30000000000000004。更多示例如下:

const sample1 = 0.2 + 0.6;
console.log(sample1); //  0.6000000000000001









const sample2 = 0.3 + 0.6;
console.log(sample2);  // 0.8999999999999999

当使用数字时,这是一个巨大的问题,因为它会给需要高精度的计算带来很大的风险。当我们使用 BigInt 时,这个问题就可以避免,因为它不接受小数。

仅当你执行 9,007,199,254,740,9912**53 – 1-9,007,199,254,740,991-(2**53 – 1) 之间的算术运算时,JavaScript 数字才能精确。所以你在该范围之外执行的任何操作都可能会导致错误的答案,例如:

const sample3 =  9_007_199_254_740_991 + 1; //  9,007,199,254,740,992






const sample4 = 9_007_199_254_740_991 + 2; // 9,007,199,254,740,992




console.log(sample3 === sample4); // true

JavaScript 对它可以表示的数字大小也有限制。它们可以用两个内置常量来表示:Number.MAX_VALUENumber.MIN_VALUE。它们本质上是数字:分别为 1.7976931348623157e+3085e-324

每当你尝试使用 Number.MAX_VALUE 执行加法或减法时,它都会返回 1.7976931348623157e+308。当你尝试对 Number.MIN_VALUE 执行相同操作时,它会返回以下示例中的值:

const sample5 = Number.MAX_VALUE + 7;
console.log(sample5); // 1.7976931348623157e+308









const sample6 = Number.MAX_VALUE - 23;
console.log(sample6); // 1.7976931348623157e+308





const sample7 = Number.MIN_VALUE + 5;
console.log(sample7); // 5


const sample8 = Number.MIN_VALUE - 54;
console.log(sample8); // -54

当你对 Number.MAX_VALUE 执行乘法 (*) 或指数 (**) 时,它总是返回无穷大。该值表示为 Number.POSITIVE_INFINITY

只有除法适用于 Number.MAX_VALUE,只有乘法适用于 Number.MIN_VALUE。当你需要执行涉及非常大的数字的计算时,这是一个严重的限制。然而,在 BigInt 的帮助下,我们可以对非常大或非常小的数字进行计算。

使用 BigInt 的优点

现在,让我们深入探讨在 JavaScript 中使用 BigInt 的优势。首先,BigInt 确保我们不会遇到精度错误。当我们处理数字时,我们可以使用小数,这可能会导致精度和舍入误差,正如我们在上一节中看到的那样。

但是,当我们使用 BigInt 时,我们不允许使用小数。这可确保始终避免此类错误。因此,执行以下操作是无效的:

const sample1 = 5.4n;
// Uncaught SyntaxError: Invalid or unexpected token

BigInt 还可以执行任意精度的计算。与在 Number.MAX_SAFE_INTEGERNumber.MIN_SAFE_INTEGER 范围之外执行计算时丢失精度的数字不同,BigInt 允许我们在不丢失任何精度的情况下执行此类计算。如下例所示:

const sample2 = BigInt(Number.MAX_SAFE_INTEGER) + 1n
const sample3 = BigInt(Number.MAX_SAFE_INTEGER) + 2n









console.log(sample2);  // 9007199254740992n




console.log(sample3); // 9007199254740993n






console.log(sample3 > sample2); // true  

而且使用 BigInt,我们可以安全、精确地对大于 Number.MAX_VALUE 的整数进行计算,因为它具有任意精度,所以它可以执行操作的整数的大小仅受主机上可用内存的限制计算机或系统。我们在使用 BigInt 时不会遇到同样的问题:

const max = BigInt(Number.MAX_VALUE);
console.log(max);
// 179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368n




const sample4 = max + 7;
// 179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858375n






const sample5 = max - 23;
// 179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858345n

此外,BigInt 允许我们为计算的位数做出限制(设置边界),从而我们可以为程序定义适当的整数范围。我们可以使用 BigInt 原型上提供的 asIntNasUintN 方法来实现这一点。

本质上,这两个方法分别用于将 BigInt 值包装为宽度位固定的二进制有符号和无符号整数。有符号整数用于表示负值,反之亦然。

因此,如果我们想将计算限制为 8 位、16 位、32 位或 64 位算术,我们可以使用 asIntN 方法通过 BigInt.asIntN(width, value) 调用它来实现。在这种情况下,width 参数表示所需的大于零的位,而 value 参数表示我们想要限制在提供的width内的 BigInt 值。

让我们看一个例子:

const maximumBits = 16;






const valueOne = BigInt(32767);
const constrainedValueOne = BigInt.asIntN(maximumBits, valueOne);
console.log(constrainedValueOne); //  32767n





const valueTwo = BigInt(32768);
const constrainedValueTwo = BigInt.asIntN(maximumBits, valueTwo);
console.log(constrainedValueTwo); // -32768n

正如你在上面的示例中看到的,我们通过使用 asIntN 创建了要使用的 16 位整数限制。一般来说,16 位只能包含 -32768n32767n 之间的整数。因此,在第一个示例中,它按原样返回值,而在后者中,它截断了该值,因为它超出了我们指定的范围。

BigInt 还提高了 JavaScript 应用程序的速度。在 ES2020 之前,开发人员依赖 Math.js 和 JSBI 等库来计算大数。然而,其中一些包又大又慢。这导致使用 JavaScript 构建的应用程序和软件执行变慢;然而,BigInt 的 出现可以改进执行大量计算的应用程序。

如何在 JavaScript 中使用 BigInt

由于 BigInt 是一种数据类型,因此它与 Number 类似,也可用于执行计算。要定义 BigInt 值,我们可以使用 BigInt 标识(即 n),或者对包含整数或数字的字符串使用 BigInt 构造函数。

但是,我们应该警惕使用构造函数将数字强制转换为 BigInt,因为即使在转换发生之前数字也可能会丢失精度。例如:

const dontDoThis = BigInt(12345567891234567890)
console.log(dontDoThis); // 12345567891234568192n









const dontDoThisToo = BigInt(`${12345567891234567890}`)
console.log(dontDoThis); // 12345567891234568000n

使用 BigInt 的方法

你可以调用 BigInt 类的五种不同的方法:

  • BigInt.prototype.toString()
  • BigInt.prototype.valueOf()
  • BigInt.prototype.toLocaleString()
  • BigInt.asIntN()
  • BigInt.asUintN()

每当遇到像 BigInt.prototype.toString() 这样的方法时,这意味着 toString() 方法将在 BigInt 对象实例(如 5n.toString())上调用,而诸如 BigInt.asUintN() 之类的方法将直接在 BigInt 构造函数上调用 BigInt.asIntN(maximumBits, valueTwo)

BigInt.prototype.toString()

首先,让我们看一下 BigInt.prototype.toString() toString() 用于将 BigInt 值转换为字符串。它本质上是用双引号或单引号包裹 BigInt,同时删除尾随的 n。它可以这样使用:

const value1 = 35n;

const sample1 = value1.toString();










console.log(sample1); // "35"

你可以使用 typeof 验证返回值现在是否为字符串,如下所示:

const value1 = 35n;

const sample1 = value1.toString();

const valueDataType = typeof sample1;




console.log(valueDataType); // "string"

或者,你还可以在将 BigInt 转换为字符串时传递基数。基数实际上是你要转换为的基数。要传递的基数范围为 2 到 36。此外,当没有传递基数时,它默认为基数 10。因此,你可以传递这样的基数:

const value = 10n;






const newValueBase16 = value.toString(16);
console.log(newValueBase16); // "a"




const newValueBase5 = value.toString(5);
console.log(newValueBase5); // "20"


const newValueBase10 = value.toString(10);
console.log(newValueBase10);// "10"

BigInt.prototype.valueOf()

valueOf 方法用于获取 BigInt 对象的原始类型。看下面的例子:

const value = Object(7n);






const valueOfDataType1 = typeof Object(7n);
console.log(valueOfDataType1); // "object"




// 你可以使用 .valueOf 方法来获取原始类型
const valueOfDataType2 = typeof Object(7n).valueOf();
console.log(valueOfDataType2); // "bigint"

BigInt.prototype.toLocaleString()

toLocaleString() 方法与 toString() 类似,但是,它可用于以特定于语言的方式或格式返回字符串。它接受两个参数,即 localeoptions。它可以这样使用:

const value = 23345689n;






const valueAsFormattedString = value.toLocaleString('en-US');
console.log(valueAsFormattedString); // 23,345,689




const valueAsFormattedStringWithOptions = value.toLocaleString('en-US', { style: 'currency', currency: 'USD' })
console.log(valueAsFormattedStringWithOptions); // $23,345,689.00

BigInt.asIntN()

BigInt.asIntN() 用于将 BigInt 值限制在设定的位宽内,同时保留值的符号。语法:BigInt.asIntN(bitWidth, BigIntValue)。因此,当我们想要保留 BigInt 值的符号(即使我们想要约束它)时,它是合适的。

一般来说,bitWidth 可以是 8 位、16 位、32 位等。因此,当我们将宽度设置为 8 位时,实质上是说我们要接受总共 256 个 BigInt 值,范围为 -128128 之间。如果该值落在该位所容纳值的范围内,则按原样返回该值;如果超出该范围,则返回与该位中的值相当的值。例如:

const bits = 8;

const value1 = 126n; // 在范围为 -128 到 128 之间
const value2 = 127n; // 在范围为 -128 到 128 之间
const value3 = 128n; // 不在范围为 -128 到 128 之间
const value4 = 129n; // 不在范围为 -128 到 128 之间
const value5 = -67n; // 在范围为 -128 到 128 之间






const result1 = BigInt.asIntN(bits, value1);
console.log(result1); //126n



const result2 = BigInt.asIntN(bits, value2);
console.log(result2); // 127n



const result3 = BigInt.asIntN(bits, value3);
console.log(result3); //  -128n



const result4 = BigInt.asIntN(bits, value4);
console.log(result4); // -127n


const result5 = BigInt.asIntN(bits, value5);
console.log(result5); // -67n

BigInt.asUintN()

BigInt.asUintN()BigInt.asIntN() 类似。主要区别在于,它忽略要约束的 BigInt 值上的符号,该符号仅约束在零和位可以包含的最大数量之间。例如,8 位可以包含 256 个值;因此,它将限制在 0 到 256 之间,其中包括 0。例如:

const bits = 8;

const value1 = 254n; // 在 0 到 256 之间
const value2 = 255n; // 在 0 到 256 之间
const value3 = 256n; // 不在 0 到 256 之间
const value4 = 257n; // 不在 0 到 256 之间
const value5 = 258n; // 不在 0 到 256 之间






const result1 = BigInt.asUintN(bits, value1);
console.log(result1); // 254n



const result2 = BigInt.asUintN(bits, value2);
console.log(result2); // 255n



const result3 = BigInt.asUintN(bits, value3);
console.log(result3); // 0n



const result4 = BigInt.asUintN(bits, value4);
console.log(result4); // 1n


const result5 = BigInt.asUintN(bits, value5);
console.log(result5); // 2n

BigInt 条件语句

适用于 Number 的条件也适用于 BigInt。这意味着 ||&&! 也可以与 BigInt 正常操作。另外,当我们使用 if 语句时,只有值 0n 才会计算为 false,而其他值则为 true

if(0n){
  console.log('It is in if')
}else{
  console.log("it is in else")
}
// "it is in else"






if(-2n){
  console.log("it is in if")
}else{
  console.log("it is in else")
}
// "it is in if"


if(67n){
   console.log("it is in if")
}else{
  console.log("it is in else")
}

// "it is in if"

即使是 Boolean 函数也能按预期运行,只有 0n 才会计算为 false,如下所示:

const isValueNone = Boolean(0n);
console.log(isValueNone); // false









const isValueExists1 = Boolean(79n);
console.log(isValueExists1);  // true





const isValueExists2 = Boolean(-65n);
console.log(isValueExists2); // true

BigInt 常见规则及注意事项

为了使用 BigInt 而不会出现错误,我们需要遵循一些规则:

  • 创建 BigInt 时不要使用 new 关键字
  • 不要在 Bigint 中使用小数
  • BigInt 强制转换错误
  • 不要直接将 JSON.stringifyBigInt 一起使用
  • 内置方法有限
  • BigInt 的除法运算返回截断的结果

首先,创建 BigInt 时不要使用 new 关键字。在 JavaScript 中,我们经常使用 new 关键字来初始化类的实例。然而,BigInt 的情况并非如此,因为当我们使用 new 关键字时,它会抛出 TypeError

const value = new BigInt(54);
// TypeError: BigInt is not a constructor

另外,请勿在 Bigint 中使用小数。当我们尝试将小数转换为 BigInt 值时,它会抛出 RangeError。类似地,当使用带有小数点的隐式转换的 BigInt 时,我们将得到一个 SyntaxError

const value = 7.8n + 9n;
// SyntaxError: Invalid or unexpected token

此外,你可能会遇到 BigInt 强制转换的错误,例如将 NumberBigInt 混合将引发 TypeError

const result = 2n + 4;
console.log(result);
// TypeError: Cannot mix BigInt and other types, use explicit conversions

并且,将 nullundefinedSymbolBigInt 一起使用将抛出错误:

console.log(BigInt(null));
// TypeError: can't convert null to BigInt









console.log(BigInt(undefined));
// TypeError: can't convert undefined to BigInt





console.log(BigInt(Symbol()));
// TypeError: can't convert Symbol() to BigInt

为了避免更多错误,请勿直接将 JSON.stringifyBigInt 一起使用。我们不能直接在 BigInt 上使用 JSON.stringify 会抛出 TypeError

const theStringified = JSON.stringify(5n);
console.log(theStringified);
// TypeError: BigInt value can't be serialized in JSON

相反,我们可以将 BigInt 值字符串化以实现 toJSON 方法或者实现一个 replacer 方法。要实现 toJSON 方法,我们只需在使用 JSON.stringify() 方法之前将以下代码添加到程序文件中:

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

要使用 replacer 方法实现 JSON.stringify() 方法,我们只需将以下代码添加到我们的程序中:

const replacer = (key, value) => {
 return typeof value === "bigint" ? value.toString() : value
}

我们可以使用以下两种方法中的任何一种来实现 JSON.stringify()

// 方法一
// 使用 toJSON









// 把这个放在使用 JSON.stringify() 的地方之前
BigInt.prototype.toJSON = function () {
  return this.toString();
};


const value1 = {one: 5n, two: 667n};
const value1ToJson = JSON.stringify(value1);
console.log(value1ToJson);
// {"one":"5","two":"667"}





// 方法二
// 使用 replacer
const replacer = (key, value) => {
 return typeof value === "bigint" ? value.toString() : value
}


const value2 = {seven: 7n, twenty: 20n, five: 5n};
const value2ToJson = JSON.stringify(value2, replacer);
console.log(value2ToJson);
// {"seven":"7","twenty":"20","five":"5"}

BigInt 的局限性

BigInt 的限制之一是我们无法在 JavaScript 中使用内置的 Math 函数。相反,我们应该自己构建数学函数。因此,执行诸如 Math.sqrt() 之类的操作将抛出 TypeError 异常:

console.log(Math.sqrt(4n))
// TypeError: can't convert BigInt to number

此外,使用 BigInt 进行除法运算会返回截断的结果。因为 BigInt 值不能是小数,而除法运算,有时会返回小数被除数,例如 7 / 4 = 1.75。因此,BigInt 值将始终舍入为 0。这意味着 7n / 4n = 1n 而不是预期的 1.75n

浏览器和 Node.js 的支持

v10.4 起的任何 Node.js 版本(包括 v18.16 等较新版本)都支持 BigInt,所以旧版本将抛出语法错误。同样,大多数现代浏览器都支持 BigInt。你可以在 caniuse 上查看详情。

截至撰写本文时,超过 94% 的浏览器支持 BigInt 构造函数及其方法。

小结

在本文中,我们了解了 BigInt、它的可用方法、用例以及使用它的问题。然而,我们应该知道,Number 对于小型网站等日常程序也很有用,并且只有当我们确定要处理大数时,才应该使用 BigInt

谢谢阅读。我希望你喜欢这篇文章,如果你有任何疑问,请务必发表评论。

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYteGY7J' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片