JS数字之旅——BigInt

763 阅读5分钟

这是我参与8月更文挑战的第7天,活动详情查看:8月更文挑战

在上一篇文章JS数字之旅——Number(上)里面提到,两个较大的数字的比较出现了不精确的问题。

23333333333333333 === 23333333333333332
// output: true
233333333333333330000000000 === 233333333333333339999999999
// output: true

那难道JavaScript就没有办法处理这种情况么?

答案肯定是有的。

BigInt

BigInt是一种可以用来表示大于Number.MAX_SAFE_INTEGER的任意整数的原始数据类型。

创建

它的字面量格式是在要表示的数字后面加上n,例如:

9007199254740991n // represent value 9007199254740991
233333333333333330000000000n // represent value 233333333333333330000000000
10n // represent value 10

同时,也可以使用类似Number(value)的方式创建一个BigInt的数字。注意:推荐使用字符串作为参数,因为较大数值时使用数字字面量的话,很大可能导致实际数值不精确的问题。

const wrongBigNum = BigInt(6666666666666666666)
// 6666666666666667008n

const bigNum = BigInt('6666666666666666666')
// 6666666666666666666n

const bigHex = BigInt('0xfffffffffffffffff')
// 295147905179352825855n

const bigOCT = BigInt('0o666666666666666666')
// 15440913008127414n

const bigBin = BigInt('0b10101011111000001111111110001010101010101000110101010111')
// 48379609165237591n

静态方法

BigInt有两个较少用到的静态方法,用来把给定的数值,转换为指定二进制位数范围内的值。

BigInt.asIntN(bits, bigNum)

将给定的BigInt类型的bigNum,转化为二进制位数为bits所表示的数值范围,并返回该数值。当超过范围的最大值的时候,将会因为越界而返回负值。

2位二进制的数值范围为 -2n1n

BigInt.asIntN(2, 1n)
// output: 1n,二进制为01
BigInt.asIntN(2, 0n)
// output: 0n,二进制为00
BigInt.asIntN(2, 2n)
// output: -2n,二进制为10
BigInt.asIntN(2, 3n)
// output: -1n,二进制为11
BigInt.asIntN(2, 4n)
// output: 0n,二进制为00

BigInt.asUintN(bits, bigNum)

将给定的BigInt类型的bigNum,转化为二进制位数为bits所表示的无符号数值范围,并返回该数值。当超过范围的最大值的时候,将会因为越界而返回0n

2位无符号二进制的数值范围为 0n3n

BigInt.asUintN(2, 0n)
// output: 0n
BigInt.asUintN(2, 1n)
// output: 1n
BigInt.asUintN(2, 2n)
// output: 2n
BigInt.asUintN(2, 3n)
// output: 3n
BigInt.asUintN(2, 4n)
// output: 0n

运算

BigInt也可以使用习惯用于Number+ - * / % ++ -- ** << >>等运算符。

let bigNum = BigInt('66666666666666666666')
// 66666666666666666666n
bigNum + 2222222222n
// output: 66666666668888888888n
bigNum - 6666666666666666666n
// output: 60000000000000000000n
bigNum * 2n
// output: 133333333333333333332n
bigNum / 3n
// output: 22222222222222222222n
bigNum % 5n
// output: 1n
console.log(++bigNum)
// output: 66666666666666666667n

但需要注意的是,参与运算的都要是BigInt,否则会触发下列错误。

Uncaught TypeError: Cannot mix BigInt and other types, use explicit conversions
    at <anonymous>:1:4

另外,如果除法运算出现不能整除的情况,会自动取整。

const bigNum = BigInt('66666666666666666666')
bigNum / 4n
// output: 16666666666666666666n

比较

正如前面提到过的,由于BigIntNumber不是相同的类型,所以不能使用===进行比较,但是可以使用==<<=>>=进行比较。

10n === 10
// output: false
10n == 10
// output: true
10n <= 18
// output: true
10n < 18
// output: true
10n >= 18
// output: false
10n > 18
// output: false

条件判断

Number一样,除了0n会转化为false以外,其他都会转化为true

序列化

由于BigInt在默认情况下不会被JSON进行序列化,所以通常会出现报错。

var obj = { name: 'Jack', sum: 10n}
JSON.stringify(obj)
VM54091:2 Uncaught TypeError: Do not know how to serialize a BigInt
    at JSON.stringify (<anonymous>)
    at <anonymous>:2:6

这时可以自行实现toJSON方法,以保证序列化能正常完成。

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

实现过后,上面的代码执行结果如下:

var obj = { name: 'Jack', sum: 10n}
JSON.stringify(obj)
// output: {"name":"Jack","sum":"10"}

性能对比 Number vs BigInt

进行1亿次的自增运算比较

Number平均耗时1.33s

let num = 0
const now = performance.now()
for (let i = 0; i < 100000000; i++) {
    num += i
}
console.log(performance.now() - now)

BigInt平均耗时13.4s

let num = 0n
const now = performance.now()
for (let i = 0n; i < 100000000n; i++) {
    num += i
}
console.log(performance.now() - now)

由此可见,BigInt为了支持任意大小整数的精确度,在性能上必然是有所损耗。因此,在Number的安全数字范围内,强烈建议保持使用Number,只有当超过安全数字范围的时候,才考虑使用BigInt

兼容性

最后,兼容性方面,在大部分现代浏览器上都能获得比较好的支持。

Screen Shot 2021-08-30 at 12.38.32 AM.png