这是我参与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位二进制的数值范围为 -2n ~ 1n
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位无符号二进制的数值范围为 0n ~ 3n
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
比较
正如前面提到过的,由于BigInt和Number不是相同的类型,所以不能使用===进行比较,但是可以使用==、<、<=、>、>=进行比较。
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。
兼容性
最后,兼容性方面,在大部分现代浏览器上都能获得比较好的支持。