Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JavaScript基本类型之--BigInt #30

Open
LiuL0703 opened this issue Oct 12, 2019 · 0 comments
Open

JavaScript基本类型之--BigInt #30

LiuL0703 opened this issue Oct 12, 2019 · 0 comments

Comments

@LiuL0703
Copy link
Owner

LiuL0703 commented Oct 12, 2019

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不需要加载解析编译的额外时间,并且在性能上表现更好。

Snipaste_2019-10-11_16-15-52-3ca05966-1757-413f-a051-29c2b22e8e62

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

现状:Number

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

    const max = Number.MAX_SAFE_INTEGER;
    // → 9_007_199_254_740_991  

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

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

    max + 1;
    // 9_007_199_254_740_992 ✅

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

    max + 2;
    // → 9_007_199_254_740_992 ❌

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

新热点:BigInt

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

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

    BigInt(Number.MAX_SAFE_INTEGER) + 2n;
    // 9_007_199_254_740_993n ✅

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

    1234567890123456789*123;
    // -> 151851850485185200000 ❌

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

    1234567890123456789n*123n;
    // -> 151851850485185185047n  ✅

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

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

    typeof 123
    // 'number'
    typeof 123n
    // 'bigint'

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

    4n === BigInt(4);
    // => true
    
    4n == 4;
    // => true
    
    4n === 4;
    // => false

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

    if(0n){
    	console.log('if');
    }else{
    	console.log('else');
    }
    
    // 输出:'else', 因为0n是假值
    
    0n || 12n
    // -> 12n
    0n && 12n
    // -> 0n
    Boolean(0n);
    // -> false
    Boolean(12n);
    // -> true
    !12n
    // -> false
    !0n
    // -> true

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

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

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

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

    BigInt(Number.MAX_SAFE_INTEGER)+2.5
    // => ?? 🤔

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

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

    1+1n
    // -> TypeError
    
    123<124n;
    // -> true

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

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

API

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

  1. BigInt函数,这个BigInt全局构造函数和Number的构造函数类似,将传入的参数转化为BigInt类型,如果转化失败,会报SyntaxError或者RangeError
    BigInt(123);
    // -> 123n
    BigInt(1.2);
    // -> RangeError
    BigInt('1.2');
    // -> SyntaxError
  1. BigInt.asIntN(width, value) BigInt.asUintN(width, value),通过这两个库函数,可以将BigInt值包装为有符号或无符号整数,并限制在特定位数。其中BigInt.asIntN(width,value)BigInt类型值包装为有符号二进制整数,BigInt.asUintN(width,value)将BigInt类型值包装为无符号二进制整数。例如:如果你要执行64位算术运算,则可以使用它们来将其保持在适当的范围内:
    // BigInt类型值所能表示的最大的有符号的64位整数值
    const max = 2n**(64n - 1n) - 1n;
    
    BigInt.asIntN(64,max);
    // -> 9_223_372_036_854_775_807n
    
    BigInt.asIntN(64, max+1n);
    // -> -9_223_372_036_854_775_808n
    //    ^ 变为负值 因为溢出了
    // 一旦传给超过64位整数范围(即63位的绝对数值+1位符号位)的BigInt值,就会发生溢出。
    
    BigInt.asUintN(64,max);
    // -> 9_223_372_036_854_775_807n
    
    BigInt.asUintN(64,max+1n)
    // -> 9_223_372_036_854_775_808n
  1. BigInt使得准确表示其他编程语言中常用的64位有符号和无符号整数成为可能,其中BigInt64ArrayBigUint64Array可以使我们更加容易且有效地表示和操作此类值的列表。
    const view = new BigInt64Array(4);
    // -> [0n,0n,0n,0n]
    view.length;
    // -> 4
    view[0];
    // -> 0n
    view[0] = 40n;
    view[0];
    // -> 40n

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

    // BigInt类型值所能表示的最大的有符号的64位整数值
    const max = 2n**(64n - 1n) - 1n;
    view[0] = max;
    view[0]
    // -> 9_223_372_036_854_775_807n
    view[0] = max + 1n;
    view[0];
    // -> -9_223_372_036_854_775_808n
    //    ^ 溢出了
    
    const view_u = new BigUint64Array(4);
    view_u[0] = max;
    view_u[0];
    // -> 9_223_372_036_854_775_807n
    view_u[0] = max+1n;
    view_u[0];
    // -> 9_223_372_036_854_775_808n

兼容性

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

参考链接:

想知道BigInt是如何在Chrome中的实现的吗?请看下篇BigInt在V8中的实现(还没写呢)loading...

@LiuL0703 LiuL0703 changed the title JavaScript基本类型之--BigInt [译]JavaScript基本类型之--BigInt Oct 12, 2019
@LiuL0703 LiuL0703 changed the title [译]JavaScript基本类型之--BigInt JavaScript基本类型之--BigInt Oct 12, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant