查看原文
其他

【第1751期】JavaScript基本类型之--BigInt

Linear-Enter 前端早读课 2019-11-23

前言

BigInt可以了解一下了。今日早读文章由跟谁学前端工程师@刘磊投稿分享。

正文从这开始~~

BigInt目前已经进入Stage 4阶段 下一个版本将会作为新特性出现在ECMAScript,下面我们来一起了解一下Bigint。

BigInt是什么? BigInt是JavaScript中一种可以用来表示任意精度整数的基本数据类型

BigInt可以用来表示任意精度整数的特性为JavaScript解锁了更多的骚操作,使用BigInt可以告别过去因为整数运算导致溢出的痛苦。特别是金融方面因为涉及大量的数据运算,比如高精度时间戳,或者数值过大的ID,这些是无法安全的用Number类型去存储的,所以退而求其次使用String类型去存储,有了BigInt类型后就可以安全的将其存储为数值类型

另外BigInt的实现也为实现BigDecimal打下坚实基础,那将对于以十进制精度表示货币金额并对其进行精确运算(也就是0.10 + 0.20 !== 0.30问题)非常有帮助

此前已经有不少库实现了BigInt式的整数存储,当BigInt完全可用时,就可以拿掉那些依赖了,因为相比于使用这些依赖库,Native BigInt则更具优势。因为与之相比,NativeBigInt不需要加载解析编译的额外时间,并且在性能上表现更好。

图示为BigInt与其他流行库在Chrome中的表现情况对比(值越大表现越好)

现状:Number

JavaScript中Number是以64位双精度浮点型存储,所以会有精度限制,JavaScript中可以准确表示的最大整数是Number.MAXSAFEINTEGER这个值是2^53-1

  1. const max = Number.MAX_SAFE_INTEGER;

  2. // → 9_007_199_254_740_991

Tips:为了可读性使用下划线作为分隔符进行分组 The numeric literal separators proposal

当自增一次时,可以得到正确值:

  1. max + 1;

  2. // 9_007_199_254_740_992 ✅

当自增两次时,我们发现结果并非预期

  1. max + 2;

  2. // → 9_007_199_254_740_992 ❌

max+1 和max+2的结果一致,这就导致我们无法保证在JavaScript中获取到的这个值的准确性,JavaScript中任何超出安全值范围的计算都会丢失精度,正因为如此我们只能信任安全值范围内的整数。

新热点:BigInt

BigInt是JavaScript中一种可以用来表示任意精度(arbitrary precision)整数的基本数据类型,使用BigInt可以安全的存储和操作任意大小的整数而不受Number类型的安全值范围的限制。

生成一个BigInt类型的值只需要在任意整数后加上n做后缀即可。例如:123 用BigInt类型表示123n,也可以通过全局函数BigInt(number)来将Number类型转化为BigInt类型,换言之BigInt(123) === 123n,让我们用BigInt来解决一下上文所提到的问题

  1. BigInt(Number.MAX_SAFE_INTEGER) + 2n;

  2. // 9_007_199_254_740_993n ✅

再看一个两个Number类型的数值相乘的例子

  1. 1234567890123456789*123;

  2. // -> 151851850485185200000 ❌

仔细看这个结果肯定是不对的,因为最低位一个是9一个是3所以正确值肯定是以7结尾的(3*9=27),但是这里却是一串0结尾,我们来用BigInt重新计算一下

  1. 1234567890123456789n*123n;

  2. // -> 151851850485185185047n ✅

很显然这次是对的,当我们用BigInt来处理时不会受到Number中的安全值范围的限制,所以不用担心精度丢失

BigInt是JavaScript中新的的基础类型,所以可以用typeof操作符去检测

  1. typeof 123

  2. // 'number'

  3. typeof 123n

  4. // 'bigint'

因为Bigint是一个单独的类型,所以BigInt类型值和Number严格模式下不相等,e.g. 4!== 4n,BigInt类型和Number类型作比较时需要将自身类型转化为相互的类型,或者直接使用严格相等(===)

  1. 4n === BigInt(4);

  2. // => true


  3. 4n == 4;

  4. // => true


  5. 4n === 4;

  6. // => false

当强制类型转化为布尔值时(例如在使用if,&&,||,或者Boolean(int)时触发),BigInt遵循和Numebr一样的规则

  1. if(0n){

  2. console.log('if');

  3. }else{

  4. console.log('else');

  5. }


  6. // 输出:'else', 因为0n是假值


  7. 0n || 12n

  8. // -> 12n

  9. 0n && 12n

  10. // -> 0n

  11. Boolean(0n);

  12. // -> false

  13. Boolean(12n);

  14. // -> true

  15. !12n

  16. // -> false

  17. !0n

  18. // -> true

BigInt支持那些常见的运算符例如:+,-,,/ * %,包括一些按位运算符如|, & , <<, >> ^ ,它和Number类型值的表现一致

  1. (7 + 6 - 5) * 4 ** 3 / 2 % 3;

  2. // → 1

  3. (7n + 6n - 5n) * 4n ** 3n / 2n % 3n;

  4. // → 1n

一元运算符-可以用来表示BigInt中的负数,如:-42n ,但是一元运算符+不支持,因为如果支持则会导致+x表示结果为非Number值,从而引起和现有逻辑的冲突。

一个值得注意的点是不要混合操作BigInt类型和Number类型,因为任何隐式强制类型转化都会导致精度丢失,看下面的例子:

  1. BigInt(Number.MAX_SAFE_INTEGER)+2.5

  2. // => ?? 🤔

可以猜一下结果,其实并没有合理的答案。因为BigInt无法表示小数,而Number则无法正确表示BigInt类型的超出安全范围的值,因此当混用BigInt和Number时会报TypeError

其中唯一的例外是比较运算符,比如 === < > <= >=等,因为这类操作符最终会返回一个布尔类型值,不存在精度丢失的情况:

  1. 1+1n

  2. // -> TypeError


  3. 123<124n;

  4. // -> true

建议:BigInt和Number一般情况下不要混合操作,BigInt对于可能操作较大整数的情况下是合理的选择,Number则对于在安全值范围内的操作更合适,所以选定一种合适的类型用下去,不要相互混用。

注意⚠️:额外需要注意的一点是无符号右移操作符>>>,因为BigInt始终是有符号的所以无符号右移操作符对于BigInt来说不会生效。

API

关于BigInt的几个API BigInt() BigInt.asIntN(width, value) BigInt.asUintN(width, value) BigInt64Array,BigUint64Array

BigInt函数,这个BigInt全局构造函数和Number的构造函数类似,将传入的参数转化为BigInt类型,如果转化失败,会报SyntaxError或者RangeError

  1. BigInt(123);

  2. // -> 123n

  3. BigInt(1.2);

  4. // -> RangeError

  5. BigInt('1.2');

  6. // -> SyntaxError

BigInt.asIntN(width, value) BigInt.asUintN(width, value),通过这两个库函数,可以将BigInt值包装为有符号或无符号整数,并限制在特定位数。其中BigInt.asIntN(width,value)将BigInt类型值包装为有符号二进制整数,BigInt.asUintN(width,value)将BigInt类型值包装为无符号二进制整数。例如:如果你要执行64位算术运算,则可以使用它们来将其保持在适当的范围内:

  1. // BigInt类型值所能表示的最大的有符号的64位整数值

  2. const max = 2n**(64n - 1n) - 1n;


  3. BigInt.asIntN(64,max);

  4. // -> 9_223_372_036_854_775_807n


  5. BigInt.asIntN(64, max+1n);

  6. // -> -9_223_372_036_854_775_808n

  7. // ^ 变为负值 因为溢出了

  8. // 一旦传给超过64位整数范围(即63位的绝对数值+1位符号位)的BigInt值,就会发生溢出。


  9. BigInt.asUintN(64,max);

  10. // -> 9_223_372_036_854_775_807n


  11. BigInt.asUintN(64,max+1n)

  12. // -> 9_223_372_036_854_775_808n

BigInt使得准确表示其他编程语言中常用的64位有符号和无符号整数成为可能,其中BigInt64Array和BigUint64Array可以使我们更加容易且有效地表示和操作此类值的列表。

  1. const view = new BigInt64Array(4);

  2. // -> [0n,0n,0n,0n]

  3. view.length;

  4. // -> 4

  5. view[0];

  6. // -> 0n

  7. view[0] = 40n;

  8. view[0];

  9. // -> 40n

BigInt64Array可以确保其值保持在有符号的64位限制范围内。BigUint64Array则确保其值保持在无符号位的64位限制范围内

  1. // BigInt类型值所能表示的最大的有符号的64位整数值

  2. const max = 2n**(64n - 1n) - 1n;

  3. view[0] = max;

  4. view[0]

  5. // -> 9_223_372_036_854_775_807n

  6. view[0] = max + 1n;

  7. view[0];

  8. // -> -9_223_372_036_854_775_808n

  9. // ^ 溢出了


  10. const view_u = new BigUint64Array(4);

  11. view_u[0] = max;

  12. view_u[0];

  13. // -> 9_223_372_036_854_775_807n

  14. view_u[0] = max+1n;

  15. view_u[0];

  16. // -> 9_223_372_036_854_775_808n

兼容性

到目前为止已经实现BigInt的有Chrome(67+),Firefox(68+),Opear(54+),Node(10.4.0+),其中Safari正在实现中

参考链接:

  • proposal-bigint

  • bigint

  • BigInt: arbitrary-precision integers in JavaScript

关于本文 作者:@刘磊 原文:https://github.com/LiuL0703/blog/issues/30

为你推荐

【第1745期】现代 JavaScript 教程 - 类型检测:"instanceof"

【第1741期】JavaScript和TypeScript中的void

【第1588期】彻底终结 Javascript 背后的隐式类型转换

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存