# BigInt
JavaScript 所有数字都保存成 64 位浮点数,这给数值的表示带来了两大限制。一是数值的精度只能到 53 个二进制位(相当于 16 个十进制位),大于这个范围的整数,JavaScript 是无法精确表示,这使得 JavaScript 不适合进行科学和金融方面的精确计算。二是大于或等于 2 的 1024 次方的数值,JavaScript 无法表示,会返回 Infinity。
ES2020 引入了一种新的数据类型 BigInt(大整数),来解决这个问题,这是 ECMAScript 的第八种数据类型。BigInt 只用来表示整数,没有位数的限制,任何位数的整数都可以精确表示。
# BigInt 类型
ES5: String, Number, Boolean, Null, Undefined
ES6 Added: Symbol, 6 types
ES10 added: BigInt, reaching 7 types
typeof 1n === 'bigint' // true
typeof BigInt('1') === 'bigint' // true
2
# BigInt & Number
过去,不支持大于 9007199254740992 的整数值。如果超过,该值将简单地锁定为 MAX_SAFE_INTEGER+1:
const limit = Number.MAX_SAFE_INTEGER
console.log(limit) // 9007199254740991
console.log(limit + 1) // 9007199254740992
console.log(limit + 2) // 9007199254740992
console.log(9007199254740993) // 9007199254740992
2
3
4
5
然后我们看看 BigInt,BigInt 只用来表示整数,没有位数的限制,任何位数的整数都可以精确表示。
const larger = 9007199254740993n
console.log(larger) // 9007199254740993n
console.log(larger + 1n) // 9007199254740994n
console.log(larger * 1000n) // 9007199254740993000n
const largerNum = BigInt(9007199254740992)
const largerNum2 = BigInt(9007199254740993)
const largerStr = BigInt('9007199254740992')
const largerStr2 = BigInt('9007199254740993')
console.log(largerNum, largerNum2, largerStr, largerStr2) // 9007199254740992n 9007199254740992n 9007199254740992n 9007199254740993n
console.log(
largerNum === largerStr,
largerNum == largerStr,
largerNum2 === largerStr
) // true true true
2
3
4
5
6
7
8
9
10
11
12
13
14
那么看看0n
&0
,1n
&1
对比如何
console.log(1n === 1, 1n == 1) // false true
console.log(0n === 0, 0n == 0) // false true
2
从上面式子不能看出 BigInt 值并不严格等于 Number 值。也就是不全等。
那两者的大小比较又会如何
1n < 2 // true
2n > 1 // true
2 > 2 // false
2n > 2 // false
2n >= 2 // true
2
3
4
5
# BigInt 计算
我们再来看看 MDN 的示例,发现 + * - % **
这些运算符都支持
const previousMaxSafe = BigInt(Number.MAX_SAFE_INTEGER)
// 9007199254740991n
const maxPlusOne = previousMaxSafe + 1n
// 9007199254740992n
const theFuture = previousMaxSafe + 2n
// 9007199254740993n, this works now!
const multi = previousMaxSafe * 2n
// 18014398509481982n
const subtr = multi - 10n
// 18014398509481972n
const mod = multi % 10n
// 2n
const bigN = 2n ** 54n
// 18014398509481984n
bigN * -1n
// -18014398509481984n
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
但是对于除法/
和数字计算有些区别:
const expected = 4n / 2n
// 2n
const truncated = 5n / 2n
// 2n, not 2.5n
console.log(expected, truncated) // 2n 2n
2
3
4
5
6
# BigInt 正负号
BigInt 可以使用负号(-),但是不能使用正号(+),因为会与 asm.js 冲突。
;-42n + // 正确
42n // 报错
2
# BigInt 函数
JavaScript 原生提供 BigInt 函数,可以用它生成 BigInt 类型的数值。转换规则基本与 Number()一致,将其他类型的值转为 BigInt。
BigInt(123) // 123n
BigInt('123') // 123n
BigInt(false) // 0n
BigInt(true) // 1n
2
3
4
BigInt()函数必须有参数,而且参数必须可以正常转为数值,下面的用法都会报错。
new BigInt() // TypeError
BigInt(undefined) //TypeError
BigInt(null) // TypeError
BigInt('123n') // SyntaxError
BigInt('abc') // SyntaxError
2
3
4
5
需要注意的是,BigInt 函数无法操作小数以及小数字符串
console.log(BigInt(123)) // 123n
BigInt(123.3) // RangeError: The number 123.3 cannot be converted to a BigInt because it is not an integer
BigInt(1.5) // RangeError
BigInt('1.5') // SyntaxError
2
3
4
BigInt 继承了 Object 对象的两个实例方法。
-- BigInt.prototype.toString()
-- BigInt.prototype.valueOf()
它还继承了 Number 对象的一个实例方法。
-- BigInt.prototype.toLocaleString()
console.log(typeof Object(1n)) // "object"
console.log(typeof Object(1n).valueOf()) // "bigint"
console.log(1024n.toString()) //"1024"
console.log(1024n.toString(2)) //"10000000000"
console.log(1024n.toString(16)) //"400"
const bigint = 123456789123456789n
// German uses period for thousands
console.log(bigint.toLocaleString('de-DE')) //"123.456.789.123.456.789"
// Request a currency format
console.log(
bigint.toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })
) // "123.456.789.123.456.789,00 €"
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
此外,还提供了三个静态方法。
-- BigInt.asUintN(width, BigInt)
: 给定的 BigInt 转为 0 到 2width - 1 之间对应的值。
-- BigInt.asIntN(width, BigInt)
:给定的 BigInt 转为 -2width - 1 到 2width - 1 - 1 之间对应的值。
-- BigInt.parseInt(string[, radix])
:近似于 Number.parseInt(),将一个字符串转换成指定进制的 BigInt。
const max = 2n ** (64n - 1n) - 1n
BigInt.asIntN(64, max)
// 9223372036854775807n
BigInt.asIntN(64, max + 1n)
// -9223372036854775808n
BigInt.asUintN(64, max + 1n)
// 9223372036854775808n
const max2 = 2n ** (64n - 1n) - 1n
BigInt.asIntN(32, max2) // -1n
BigInt.asUintN(32, max2) // 4294967295n
// Number.parseInt() 与 BigInt.parseInt() 的对比
Number.parseInt('9007199254740993', 10)
// 9007199254740992
BigInt.parseInt('9007199254740993', 10)
// 9007199254740993n
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Math 对象扩展
ES6 在 Math 对象上新增了 17 个与数学相关的方法。所有这些方法都是静态方法,只能在 Math 对象上调用。
# Math.trunc()
Math.trunc()静态方法通过删除任何小数返回数字的整数部分。
console.log(Math.trunc(13.37)) //13
console.log(Math.trunc(42.84)) //42
console.log(Math.trunc(0.123)) //0
console.log(Math.trunc(-0.123)) // -0
console.log(Math.trunc('-1.123')) // -1
2
3
4
5
6
7
8
9
一些特殊的,但是能转成数值的处理
Math.trunc(true) //1
Math.trunc(false) // 0
Math.trunc(null) // 0
2
3
对于空值和无法截取整数的值,返回 NaN。
Math.trunc(NaN) // NaN
Math.trunc('foo') // NaN
Math.trunc() // NaN
Math.trunc(undefined) // NaN
2
3
4
处理Infinity/-Infinity
,返回原值。
Math.trunc(-Infinity) // -Infinity
Math.trunc(Infinity) // Infinity
2
# Math.sign()
Math.sign 方法用来判断一个数到底是正数、负数、还是零。对于非数值,会先将其转换为数值。
它会返回五种值。
- 参数为正数,返回+1;
- 参数为负数,返回-1;
- 参数为 0,返回 0;
- 参数为-0,返回-0;
- 其他值,返回 NaN。
Math.sign(3) // 1
Math.sign(-3) // -1
Math.sign('-3') // -1
Math.sign(0) // 0
Math.sign(-0) // -0
Math.sign(NaN) // NaN
Math.sign('foo') // NaN
Math.sign() // NaN
2
3
4
5
6
7
8
如果参数是非数值,会自动转为数值。对于那些无法转为数值的值,会返回 NaN。
Math.sign('') // 0
Math.sign(true) // +1
Math.sign(false) // 0
Math.sign(null) // 0
Math.sign('9') // +1
Math.sign('foo') // NaN
Math.sign() // NaN
Math.sign(undefined) // NaN
2
3
4
5
6
7
8
# Math.hypot()
Math.hypot 方法返回所有参数的平方和的平方根。 如果参数不是数值,Math.hypot 方法会将其转为数值。只要有一个参数无法转为数值,就会返回 NaN。
Math.hypot(3, 4) // 5
Math.hypot(3, 4, 5) // 7.0710678118654755
Math.hypot() // 0
Math.hypot(NaN) // NaN
Math.hypot(3, 4, 'foo') // NaN
Math.hypot(3, 4, '5') // 7.0710678118654755
Math.hypot(-3) // 3
2
3
4
5
6
7
# Math.cbrt()
Math.cbrt()方法用于计算一个数的立方根。
Math.cbrt(-1) // -1
Math.cbrt(0) // 0
Math.cbrt(1) // 1
Math.cbrt(2) // 1.2599210498948732
2
3
4
对于非数值,Math.cbrt()方法内部也是先使用 Number()方法将其转为数值。
Math.cbrt('8') // 2
Math.cbrt('hello') // NaN
2
# 32 位转换方法
Math.clz32()
方法将参数转为 32 位无符号整数的形式,然后返回这个 32 位值里面有多少个前导 0。Math.imul
方法返回两个数以 32 位带符号整数形式相乘的结果,返回的也是一个 32 位的带符号整数。Math.fround
方法返回一个数的 32 位单精度浮点数形式。
# 对数方法
Math.expm1(x)
返回 ex - 1,即 Math.exp(x) - 1。Math.log1p(x)
方法返回 1 + x 的自然对数,即 Math.log(1 + x)。如果 x 小于-1,返回 NaN。Math.log10(x)
返回以 10 为底的 x 的对数。如果 x 小于 0,则返回 NaN。Math.log2(x)
返回以 2 为底的 x 的对数。如果 x 小于 0,则返回 NaN。
# 双曲函数方法
Math.sinh(x)
返回 x 的双曲正弦(hyperbolic sine)Math.cosh(x)
返回 x 的双曲余弦(hyperbolic cosine)Math.tanh(x)
返回 x 的双曲正切(hyperbolic tangent)Math.asinh(x)
返回 x 的反双曲正弦(inverse hyperbolic sine)Math.acosh(x)
返回 x 的反双曲余弦(inverse hyperbolic cosine)Math.atanh(x)
返回 x 的反双曲正切(inverse hyperbolic tangent)
# 二进制和八进制表示法
在 ES6 之前,对于整数的二进制表示,您最好的选择是将它们传递给基数为 2 的 parseInt。
parseInt('101', 2) //5
ES6 提供了二进制和八进制数值的新的写法,分别用前缀 0b(或 0B)和 0o(或 0O)表示。
0b111110111 === 503 // true
0o767 === 503 // true
0b111110111 === 503 // true
0o767 === 503 // true
2
3
4
ES6 延续了对以 10 为底的数字之外的数字文字表示方式的更改/变化。现在有了官方的八进制形式、修正的十六进制形式和全新的二进制形式。
let dec = 42,
oct = 0o52, // or `0O52`
hex = 0x2a, // or `0X2a`
bin = 0b101010 // or `0B101010`
2
3
4
用 Number 处理这些字符串类型会被转化成字符串形式,
Number('42') // 42
Number('0o52') // 42
Number('0x2a') // 42
Number('0b101010') // 42
2
3
4
那么反过来处理会如何显示:
const a = 42
a.toString() // "42" -- also `a.toString( 10 )`
a.toString(8) // "52"
a.toString(16) // "2a"
a.toString(2) // "101010"
2
3
4
5
6
# 数值分隔符
ES2021,允许 JavaScript 的数值使用下划线(_)作为分隔符。
let num = 12_345;
console.log(num) // 12345
num.toString() // '12345'
2
3
数值分隔符有几个使用注意点。
- 不能放在数值的最前面(leading)或最后面(trailing)。
- 不能两个或两个以上的分隔符连在一起。
- 小数点的前后不能有分隔符。
- 科学计数法里面,表示指数的 e 或 E 前后不能有分隔符。
// 全部报错
3_.141
3._141
1_e12
1e_12
123__456
_1464301
1464301_
2
3
4
5
6
7
8
除了十进制,其他进制的数值也可以使用分隔符。
console.log(0b10_0) // 4
console.log(0b10_0) // 4
console.log(0o10_0) // 64
console.log(0o10_0) // 64
console.log(0x10_0) // 256
2
3
4
5