JavaScript 中的表示任意精度的 BigInt

做为前端开发,不知道你们是否被大整数困扰过?JavaScript 对大整型一直没有支持,想要操做大整型数字必须借助第三方库,除了麻烦还可能有打包过大和运行时效率的问题。对比 Java 中,早就有了能表示任意精度的BigInteger。而对于 JavaScript,ECMAScript 中的提案 BigInt 就是一个能够表示任意精度的新的数字原始类型html

本文主要围绕 BigInt 讲讲其现状、特性、进展和目前的使用方法。前端

Number 类型的局限性

JavaScript 中的 Number 是双精度浮点型,这意味着精度有限。Number.MAX_SAFE_INTEGER 就是安全范围内的最大值,为 2**53-1。最小安全值为 Number.MIN_SAFE_INTEGER 值为 -((2**53)-1)。超出安全值的计算都会丧失精度。以下,能够看到 max + 1max + 2 的值相同,这显然是不对的。git

const max = Number.MAX_SAFE_INTEGER; // 9007199254740991
max + 1 // 9007199254740992
max + 2 // 9007199254740992
复制代码

至于为何最大安全值是 2**53-1,与 IEEE 754 的 float 浮点数存储有关,可参考抓住数据的小尾巴 - JS浮点数陷阱及解法github

实际应用中,例如在大整数 ID、高精度时间戳中会致使不安全的问题。Twitter IDs (snowflake)文中说到 Twitter 的 id 生成服务,当 id 持续增加时,就会超出 JS 的安全范围,所以要求同时冗余地返回字符串型的 id。另外一个例子,高精度时间戳在运算的时候也会丧失精度,例如使用 performance 对象与 BigInt 结合,能够获取精确到皮秒的时间戳(固然这个时间戳是否是真的精准是另外一个问题),代码以下:web

// 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 与其余相似第三方库的性能对比。typescript

BigInt 与其余相似第三方库的性能对比

BigInt 的特性

BigInt 是一个新的原始类型,能够实现任意精度计算。建立 BigInt 类型的值也很是简单,只须要在数字后面加上 n 便可。例如,789 变为 789n。也能够使用全局方法 BigInt(value) 转化,入参 value 为数字或数字字符串。例如:浏览器

BigInt(1234567890) === 1234567890n // true
复制代码

另外一个例子就是上述的时间戳转换。安全

新的原始类型

既然 BigInt 是一个新的原始类型,那么它就能够使用 typeof 检测出本身的类型bash

typeof 111 // "number"
typeof 111n // "bigint"
复制代码

同时 BigIntNumber 类型的值也是不严格相等的。babel

111 === 111n // false
111 == 111n // true
复制代码

在数字布尔间的逻辑中,BigIntNumber 表现一致。

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 或异常状况。

另外就是不能混合使用 BigIntNumber 计算,例以下面的结果会抛出异常:

1 + 1n
// Uncaught TypeError: Cannot mix BigInt and other types, use explicit conversions
复制代码

因为不能混合使用 BigIntNumber,你也不能图省事将代码中全部的 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 特性了。

参考

相关文章
相关标签/搜索