作为前端开发,不知道大家是否被大整数困扰过?JavaScript 对大整型一直没有支持,想要操作大整型数字必须借助第三方库,除了麻烦还可能有打包过大和运行时效率的问题。对比 Java 中,早就有了能表示任意精度的BigInteger
。而对于 JavaScript,ECMAScript 中的提案 BigInt
就是一个可以表示任意精度的新的数字原始类型。
本文主要围绕 BigInt
讲讲其现状、特性、进展和目前的使用方法。
Number 类型的局限性
JavaScript 中的 Number
是双精度浮点型,这意味着精度有限。Number.MAX_SAFE_INTEGER
就是安全范围内的最大值,为 2**53-1
。最小安全值为 Number.MIN_SAFE_INTEGER
值为 -((2**53)-1)
。超出安全值的计算都会丧失精度。如下,可以看到 max + 1
与 max + 2
的值相同,这显然是不对的。
const max = Number.MAX_SAFE_INTEGER; // 9007199254740991
max + 1 // 9007199254740992
max + 2 // 9007199254740992
复制代码
至于为什么最大安全值是 2**53-1
,与 IEEE 754 的 float 浮点数存储有关,可参考抓住数据的小尾巴 - JS浮点数陷阱及解法。
实际应用中,例如在大整数 ID、高精度时间戳中会导致不安全的问题。Twitter IDs (snowflake)文中说到 Twitter 的 id 生成服务,当 id 持续增长时,就会超出 JS 的安全范围,因此要求同时冗余地返回字符串型的 id。另一个例子,高精度时间戳在运算的时候也会丧失精度,例如使用 performance
对象与 BigInt
结合,可以获取精确到皮秒的时间戳(当然这个时间戳是不是真的精准是另一个问题),代码如下:
// 1 毫秒(ms) = 1,000 微秒(μs) = 1,000,000 纳秒(ns) = 1,000,000,000 皮秒(ps)
const scale = 1000000000
const scaleBig = 1000000000n
const big = BigInt((performance.now() * scale).toFixed(0)) + BigInt(performance.timing.navigationStart) * scaleBig
const normal = (performance.now() + performance.timing.navigationStart) * scale
console.log(big) // 1550488515092440117252n 精确到皮秒
console.log(normal) // 1.550488515092455e+21 精确到微秒
复制代码
在没有 BigInt 的时候,如果想要使用大整型,则不得不借助类似 BigInt 功能的第三方库。这有可能会影响 JavaScript 程序的效率,比如加载时间、解析时间、编译时间,以及运行时的效率。下图为 BigInt
与其他类似第三方库的性能对比。
BigInt 的特性
BigInt
是一个新的原始类型,可以实现任意精度计算。创建 BigInt
类型的值也非常简单,只需要在数字后面加上 n
即可。例如,789
变为 789n
。也可以使用全局方法 BigInt(value)
转化,入参 value 为数字或数字字符串。例如:
BigInt(1234567890) === 1234567890n // true
复制代码
另一个例子就是上述的时间戳转换。
新的原始类型
既然 BigInt
是一个新的原始类型,那么它就可以使用 typeof
检测出自己的类型
typeof 111 // "number"
typeof 111n // "bigint"
复制代码
同时 BigInt
与 Number
类型的值也是不严格相等的。
111 === 111n // false
111 == 111n // true
复制代码
在数字布尔间的逻辑中,BigInt
与 Number
表现一致。
if (0n) {
console.log('if');
} else {
console.log('else');
}
// → logs 'else', because `0n` is falsy.
复制代码
如果算上 BigInt
,JavaScript 中原始类型就从 6 个变为了 7 个。
- Boolean
- Null
- Undefined
- Number
- String
- Symbol (new in ECMAScript 2015)
- BigInt (new in future ECMAScript)
运算
BigInt
支持绝大部分常用运算符,+
, -
, *
, /
, %
, 和 **
。
位运算符 |, &, <<, >>, ^
表现也与 Number
类型中一致。
一元运算符 -
表示负数,但是 +
不能用于表示正数。因为在 webAssembly(asm.js) 中,+x
始终表示一个 Number
或异常情况。
另外就是不能混合使用 BigInt
与 Number
计算,例如下面的结果会抛出异常:
1 + 1n
// Uncaught TypeError: Cannot mix BigInt and other types, use explicit conversions
复制代码
由于不能混合使用 BigInt
与 Number
,你也不能图省事将代码中所有的 Number
都用 BigInt
代替。需要视情况而定,如果数字有可能变得很大,那么再决定使用 BigInt
。
API
-
BigInt()
构造函数,类似Number()
,可以将入参转化为BigInt
类型。BigInt(1) // 1n BigInt(1.5) // RangeError BigInt('1.5') // SyntaxError 复制代码
-
BigInt64Array 和 BigUint64Array
同时
BigInt
也可以精确表示64位有符号和无符号整型,所有有两个新的 TypedArray 即 BigInt64Array 和 BigUint64Array。const view = new BigInt64Array(4); // → [0n, 0n, 0n, 0n] view.length; // → 4 view[0]; // → 0n view[0] = 42n; view[0]; // → 42n 复制代码
ECMAScript TC39 进展
目前 ES2019 的新特性都已经确定,见 Twitter -New JavaScript features in ES2019,没有 BigInt,如下图:
➡️ Array#{flat,flatMap}
➡️ Object.fromEntries
➡️ String#{trimStart,trimEnd}
➡️ Symbol#description
➡️ try { } catch {} // optional binding
➡️ JSON ⊂ ECMAScript
➡️ well-formed JSON.stringify
➡️ stable Array#sort
➡️ revised Function#toString
复制代码
同时可以在 github 上 tc39 已完成的草案中看到。
BigInt
目前处于 Stage 3 阶段,问题不大的话,ES2020 中应该被收录。
支持情况 & PolyFill
目前(201902)浏览器支持情况并不理想,只有 Chrome 支持较好,其他浏览器支持不好。由于和其他 JavaScript 新特性不同,BigInt 不能很好的被编译为 ES5。因为 BigInt 中修改了运算符的工作行为,这些行为是不能直接被 polyfill 转换的。
但是可以使用一个库 the JSBI library,来实现 BigInt。JSBI 是直接使用了 V8 和 Chrome 中 BigInt 的设计和实现方式,功能与浏览器中一致,语法稍有不同:
import JSBI from './jsbi.mjs';
const max = JSBI.BigInt(Number.MAX_SAFE_INTEGER);
const two = JSBI.BigInt('2');
const result = JSBI.add(max, two);
console.log(result.toString());
// → '9007199254740993'
复制代码
一旦 BigInt 被所有的浏览器原生支持后,可以使用 babel 插件 babel-plugin-transform-jsbi-to-bigint移除 JSBI 转为原生的 BigInt 语法。例如上述代码会被转为:
const max = BigInt(Number.MAX_SAFE_INTEGER);
const two = 2n;
const result = max + two;
console.log(result);
// → '9007199254740993'
复制代码
TypeScript 支持
TypeScript 3.2 已经加入了 BigInt
的类型校验。将 tsconfig 配置为 target: esnext
即可。用法示例如下:
let foo: bigint = BigInt(100); // the BigInt function
let bar: bigint = 100n; // a BigInt literal
// *Slaps roof of fibonacci function*
// This bad boy returns ints that can get *so* big!
function fibonacci(n: bigint) {
let result = 1n;
for (let last = 0n, i = 0n; i < n; i++) {
const current = result;
result += last;
last = current;
}
return result;
}
fibonacci(10000n)
复制代码
小结
如果你确定你的页面只跑在最新的 Chrome 中,那么现在就可以大胆的使用 BigInt
了,更优雅高效的处理大数据。若在其他浏览器中需要支持,可以使用 JSBI 这个库,日后甩掉它的姿势也十分优雅。
看着 JavaScript 越来越健壮,甚是欣喜。随着端计算能力的强大,AI 的发展,说不定很快就能用到这个 BigInt
特性了。