ECMAScript6-基础6-数值存储与数据扩展

JS与IEEE754标准

概念

由于不同机器所选用的基数、尾数位长度和阶码位长度不同,因此对浮点数的表示有较大差别,这不利于软件在不同计算机之间的移植。为此,美国IEEE(电器及电子工程师协会)提出了一个从系统角度支持浮点数的表示方法,称为IEEE754标准(IEEE,1985),当今流行的计算机几乎都采用了这一标准;

Bug

Java中浮点数,既float和double,都是采用的IEEE754标准。无论在java、python、javaScript里面都存在 1 + 2 != 3 问题,这个问题的产生根源在于计算存储数字是二进制,对无限循环小数和无理数采用双精度64位double浮点数_float为32位,即52位小数+11位指数+1位符号。超过52位小数溢出而产生精度丢失。

这个问题并不只是在Javascript中才会出现,任何使用二进制浮点数的编程语言都会有这个问题,只不过在 C++/C#/Java 这些语言中已经封装好了方法来避免精度的问题,而 JavaScript 是一门弱类型的语言,从设计思想上就没有对浮点数有个严格的数据类型,所以精度误差的问题就显得格外突出。

造成的原因

JS中的数字类型只有Number一种,Number类型采用IEEE754标准中的“双精度浮点数”来表示一个数字,不区分整数和浮点数,即所有数字都采用双精度浮点数方式来存储;

造成的原因:十进制整数转二进制

十进制整数转二进制,是没任何问题的,就以数字6为例:

6/2=3...0

3/2=1...1

1/2=0...1

所以,6的二进制就是110,总之一句话:十进制整数总是能正确的用二进制来表达;

造成的原因:十进制浮点数转二进制

相对于十进制整数总能正确转为二进制,十进制小数却有时不能正确转为二进制;

以0.8125为例来进行说明:

0.8125 * 2 = 1.625 取整是1

0.625 * 2 = 1.25    取整是1

0.25 * 2 = 0.5       取整是0

0.5 * 2 = 1.0        取整是1

即0.8125的二进制是0.1101(第一次所得到为最高位,最后一次得到为最低位)

那么,看一下0.1对应的二进制表示:

0.1*2=0.2======取出整数部分0

0.2*2=0.4======取出整数部分0

0.4*2=0.8======取出整数部分0

0.8*2=1.6======取出整数部分1

0.6*2=1.2======取出整数部分1

0.2*2=0.4======取出整数部分0

0.4*2=0.8======取出整数部分0

0.8*2=1.6======取出整数部分1

0.6*2=1.2======取出整数部分1

0.2*2=0.4======取出整数部分0

0.4*2=0.8======取出整数部分0

0.8*2=1.6======取出整数部分1

0.6*2=1.2======取出整数部分1

这里,加粗字体会不断重复,直到超过“双精度浮点数”长度,没办法,就只能进行截取,可问题在于,一旦截短,那肯定比原先的0.1要大;

科学计数法

科学记数法是一种把一个数表示成a与10的n次幂相乘的形式(1≤a<10,n为整数)的记数法。;

例如:19971400000000=1.99714×10^13。计算器或电脑表达10的幂是一般是用E或e,也就是1.99714E13=19971400000000;

科学记数法的形式是由两个数的乘积组成的。表示为a×10^b(aEb),其中一个因数为a(1≤|a|<10),另一个因数为10^n。

科学计数法的精确度

运用科学记数法a×10^n的数字,它的精确度以a的最后一个数在原数中的数位为准;

13600,精确到十位,记作:1.360X10^4

13600 ,精确到百位,记作:1.36X10^4

13600,精确到千位,记作:1.3X10^4

十进制的5.0,写成二进制是101.0,相当于1.01×2^2

十进制的-5.0,写成二进制是-101.0,相当于-1.01×2^2,推荐阅读《浮点数的二进制表示 》

在二进制里面,即a×2^b,1≤a<2,也就是说,a可以写成1.xxxxxx的形式,其中xxxxxx表示小数部分。IEEE 754规定,在计算机内部保存a时,默认这个数的第一位总是1,因此可以被舍去,只保存后面的xxxxxx部分。比如保存1.01的时候,只保存01,等到读取的时候,再把第一位的1加上去。这样做的目的,是节省1位有效数字。以64位浮点数为例,留给a只有52位,将第一位的1舍去以后,等于可以保存53位有效数字

所以js浮点数表示的数字为[-1*2^53*2^1023,1*2^53*2^1024],

±1为符号位

2^53为小数位(53-1)

2^1024中1024位指数位(1024*2=2048=2^11为指数位)

浮点数表示法存储结构

在 IEEE754 中,双精度浮点数采用 64 位存储,即 8 个字节表示一个浮点数 。其存储结构如下图所示:

1位符号位、11位指数位、52位小数位

这里的11位指数位,取值范围是2^11 = 2048,那么在科学表达式a×2^b中,b的取值范围就是0-2048;

指数位

双精度浮点数中有11位作为指数为,即11个二进制单位,指数位把全0和全1的情况,单独拿出来干别的了,也就是2^11 = 2048,先把特殊数字除去,那就剩下2047个数字;

指数位:00000000

位长:11

表示范围:0<=e<=(2^11)-1,即等于等于0,小于等于2047

为了表示负指数,以中间值进行偏移:bias = 1023

偏移结果:-1023<=e-bias<=1024

为了表示正负指数,拿出一位来当做符号位,那就剩下(11-1=10)来作为数值,原先是2^11来作为取值取值范围,且都为正数;当拿出1位作为符号位之后,取值范围变成:

从0000-0000-000到1111-1111-111

变成

0111-1111-111到0000-0000-000,即-1023到-0

1000-0000-000到1111-1111-111,即+0到1023

那么,我们可以推导出双精度浮点数的计算公式:

Value = -1^s * 2^E * (1.f)

E = e - bias

这里的s就是64位的第一位符号位

这里的E就是64位中的11位指数位

这里的f就是52位数字精确度位

e原先的取值范围是:0<=e<=(2^11)-1=2047

为了分别出正指数与负指数,用了一位符号位,最小的负数是0111-1111-111,即-1023,即bias=1023,那么,引入偏移之后的e取值范围是:-1023<=e-bias<=1024

当e-bias=-1023,表示正负0,或者,非规格浮点数

当e-bias=1024,表示正负无穷,或者NaN

总结如下:在科学表达式a×2^b中,

①b能获取的最大值是2047(2^11-1)

②取中间值进行偏移,用来表示负指数来表示小数,也就是说b的取值范围是 [-1023,1024]

即,11位的指数位能够表示的数值范围为 2^1024到2^-1023,超出这个范围的数无法表示。2^1024 和2^-1023转换为科学计数法如下所示:

2^1024 = 1.7976931348623157 * 10^308

2^-1023 = 5 * 10^-324

因此,JS中能表示的最大值Number.MAX_VALUE=1.7976931348623157*10^308,最小值Number.MIN_VALUE=5*10^-324,JAVA双精度类型double也是如此。

如果数字超过最大值或最小值,JS将返回一个不正确的值,这称为“正向溢出(overflow)”/“负向溢出(underflow)”;

双精度浮点数总结

在 64 位的二进制中:

1位符号位决定了一个数的正负

11位指数位决定了数值的大小

52位小数位决定了数值的精度

IEEE754规定,小数位第一位默认总是1,因此,在表示精度的位数前面,还存在一个“隐藏位”固定为1 ,但它不保存在 64位浮点数之中。也就是说,有效数字总是1.xx...xx的形式,其中xx..xx 的部分保存在64位浮点数之中,最长为52位。所以,JS提供的有效数字最长为53个二进制位,其内部实际的表现形式为:

(-1)^符号位 * 1.xx...xx * 2^指数位

这意味着,JS能表示并进行精确算术运算的整数范围为:[-2^53-1,2^53-1],即从最小值Number.MIN_SAFE_INTEGER=-9007199254740991到最大值Number.MAX_SAFE_INTEGER= 9007199254740991之间的范围;

数值的扩展

概念

从ES5开始,在严格模式之中,八进制就不再允许使用前缀0表示,到了ES6 进一步明确,要使用前缀0o表示;(第一个是数字0,第二个是字母o)

ES6 提供了二进制和八进制数值的新的写法:分别用前缀0b(或0B)和0o(或0O)表示;

案例讲解/注意事项

0b111110111 === 503 // true

0o767 === 503 // true

 

 

// 非严格模式

(function(){

  console.log(0o11 === 011);

})() // true

// 严格模式

(function(){

  'use strict';

  console.log(0o11 === 011);

})() // Uncaught SyntaxError: Octal literals are not allowed in strict mode.

 

 

如果要将0b和0o前缀的字符串数值转为十进制,要使用Number方法:

Number('0b111')  // 7

Number('0o10')  // 8

Number.isFinite()/Number.isNaN()

概念

ES6为Number对象新增Number.isFinite()、Number.isNaN()方法;

Number.isFinite():用来检查一个数值是否为有限的(finite),即不是Infinity;如果参数类型不是数值,Number.isFinite一律返回false;

Number.isNaN():用来检查一个值是否为NaN;如果参数类型不是NaN,Number.isNaN一律返回false;

案例讲解/注意事项

Number.isFinite(15); // true

Number.isFinite(0.8); // true

Number.isFinite(NaN); // false

Number.isFinite(Infinity); // false

Number.isFinite(-Infinity); // false

Number.isFinite('foo'); // false

Number.isFinite('15'); // false

Number.isFinite(true); // false

 

 

Number.isNaN(NaN) // true

Number.isNaN(15) // false

Number.isNaN('15') // false

Number.isNaN(true) // false

Number.isNaN(9/NaN) // true

Number.isNaN('true' / 0) // true

Number.isNaN('true' / 'true') // true

 

 

与之前全局方法isFinite()和isNaN()的区别在于:传统方法先调用Number()将非数值的值转为数值,然后再进行判断;

而这两个新增方法只对数值有效,Number.isFinite()对于非数值一律返回false, Number.isNaN()只有对于NaN才返回true,非NaN一律返回false:

isFinite(25) // true

isFinite("25") // true

Number.isFinite(25) // true

Number.isFinite("25") // false

 

isNaN(NaN) // true

isNaN("NaN") // true

Number.isNaN(NaN) // true

Number.isNaN("NaN") // false

Number.isNaN(1) // false

 

即:传统全局方法isFinite()和isNaN()会利用Number()对参数先做一次转换,再去判断,而新增的Number.isFinite()和Number.isNaN()则直接对参数进行判断;

Number.parseInt()/Number.parseFloat()

概念

ES6将全局方法parseInt()、parseFloat()移植到Number对象上面,行为完全保持不变;这样做的目的是逐步减少全局性方法,使得语言逐步模块化;

案例讲解/注意事项

// ES5的写法

parseInt('12.34') // 12

parseFloat('123.45#') // 123.45

 

// ES6的写法

Number.parseInt('12.34') // 12

Number.parseFloat('123.45#') // 123.45

 

Number.parseInt === parseInt // true

Number.parseFloat === parseFloat // true

Number.isInteger()

概念

Number.isInteger()用来判断一个数值是否为整数;由于JS内部中整数和浮点数采用的是同样的储存方法,所以25和25.0被视为同一个值;如果参数不是数值,Number.isInteger返回false;

案例讲解/注意事项

Number.isInteger(25) // true

Number.isInteger(25.1) // false

Number.isInteger(25) // true

Number.isInteger(25.0) // true

 

 

Number.isInteger() // false

Number.isInteger(null) // false

Number.isInteger('15') // false

Number.isInteger(true) // false

 

 

由于JS采用IEEE 754 标准,数值存储为64位双精度格式,数值精度最多可以达到 53 个二进制位(1个隐藏位与 52个有效位)。如果数值的精度超过这个限度,第54位及后面的位就会被丢弃,这种情况下,Number.isInteger可能会误判,例如:

Number.isInteger(3.0000000000000002) // true

Number.isInteger的参数明明不是整数,但是会返回true。原因就是这个小数的精度达到了小数点后16个十进制位,转成二进制位超过了53个二进制位,导致最后的那个2被丢弃了;

类似的情况还有,如果一个数值的绝对值小于Number.MIN_VALUE(5E-324),即小于 JS能够分辨的最小值,会被自动转为 0。这时,Number.isInteger也会误判:

Number.isInteger(5E-324) // false

Number.isInteger(5E-325) // true

 

 

总之,如果对数据精度的要求较高,不建议使用Number.isInteger()判断一个数值是否为整数;

Number.EPSILON

概念

ES6在Number对象上面,新增一个极小的常量Number.EPSILON,根据规格它表示1与大于1的最小浮点数之间的差值;

对于64位浮点数来说,大于1的最小浮点数相当于二进制的1.00...001,小数点后面有连续51个零。这个值减去1之后,就等于2的-52 次方;

Number.EPSILON实际上是JS能够表示的最小精度,误差如果小于这个值,就可以认为已经没有意义了,即不存在误差了;

案例讲解/注意事项

Number.EPSILON === Math.pow(2, -52)

// true  等于2的-52 次方

Number.EPSILON

// 2.220446049250313e-16

Number.EPSILON.toFixed(20)

// "0.00000000000000022204"

为什么新增这项技术/应用场景

引入一个这么小的量的目的,在于为浮点数计算时,设置一个误差范围;

我们知道浮点数计算是不精确的,

Number.EPSILON可以用来设置“能够接受的误差范围”。比如,误差范围设为 2 的-50 次方(即Number.EPSILON * Math.pow(2, 2)),即如果两个浮点数的差小于这个值,我们就认为这两个浮点数相等。

因此,Number.EPSILON的实质是一个可以接受的最小误差范围。

 

 

0.1 + 0.2

// 0.30000000000000004

 

0.1 + 0.2 - 0.3

// 5.551115123125783e-17

 

5.551115123125783e-17.toFixed(20)

// '0.00000000000000005551'

 

0.1 + 0.2 === 0.3 // false

 

 

 

5.551115123125783e-17 < Number.EPSILON * Math.pow(2, 2)

// true

 

 

function withinErrorMargin (left, right) {

  return Math.abs(left - right) < Number.EPSILON * Math.pow(2, 2);

}

 

0.1 + 0.2 === 0.3 // false

withinErrorMargin(0.1 + 0.2, 0.3) // true

 

1.1 + 1.3 === 2.4 // false

withinErrorMargin(1.1 + 1.3, 2.4) // true

 

 

 

安全整数和 Number.isSafeInteger()

概念

JS能够准确表示的整数范围在-2^53到2^53之间(不含两个端点),超过这个范围,无法精确表示这个值;

ES6 引入了Number.MAX_SAFE_INTEGER和Number.MIN_SAFE_INTEGER这两个常量,用来表示这个范围的上下限;

Number.isSafeInteger()则是用来判断一个整数是否落在这个范围之内;这个函数的实现很简单,就是跟安全整数的两个边界值比较一下,实际使用这个函数时,需要注意。验证运算结果是否落在安全整数的范围内,不要只验证运算结果,而要同时验证参与运算的每个值;

举个例子:

9007199254740993 - 990 = 9007199254740003

但由于JS只能识别到90071992547409932,所以,最后这个减法运算结果是9007199254740002,且Number.isSafeInteger(9007199254740993 - 990)结果为true;

所以,即便我们看到Number.isSafeInteger为true,不代表表达式是没问题的,还需要分别验证9007199254740993 与 990 的合法性;

所以,如果只验证运算结果是否为安全整数,很可能得到错误结果,还需要同时验证运算数和运算结果;

案例讲解/注意事项

JS能够准确表示的整数范围在-2^53到2^53之间(不含两个端点),超过这个范围,无法精确表示这个值:

Math.pow(2, 53) // 9007199254740992

9007199254740992  // 9007199254740992

9007199254740993  // 9007199254740992

Math.pow(2, 53) === Math.pow(2, 53) + 1

// true

 

Number.MAX_SAFE_INTEGER === Math.pow(2, 53) - 1

// true

Number.MAX_SAFE_INTEGER === 9007199254740991

// true

Number.MIN_SAFE_INTEGER === -Number.MAX_SAFE_INTEGER

// true

Number.MIN_SAFE_INTEGER === -9007199254740991

// true

 

Number.isSafeInteger('a') // false

Number.isSafeInteger(null) // false

Number.isSafeInteger(NaN) // false

Number.isSafeInteger(Infinity) // false

Number.isSafeInteger(-Infinity) // false

 

Number.isSafeInteger(3) // true

Number.isSafeInteger(1.2) // false

Number.isSafeInteger(9007199254740990) // true

Number.isSafeInteger(9007199254740992) // false

 

Number.isSafeInteger(Number.MIN_SAFE_INTEGER - 1) // false

Number.isSafeInteger(Number.MIN_SAFE_INTEGER) // true

Number.isSafeInteger(Number.MAX_SAFE_INTEGER) // true

Number.isSafeInteger(Number.MAX_SAFE_INTEGER + 1) // false

 

 

 

 

Number.isSafeInteger(9007199254740993)

// false

Number.isSafeInteger(990)

// true

Number.isSafeInteger(9007199254740993 - 990)

// true

9007199254740993 - 990

// 返回结果 9007199254740002

// 正确答案应该是 9007199254740003

上面代码中,9007199254740993不是一个安全整数,但是Number.isSafeInteger会返回结果,显示计算结果是安全的。这是因为,这个数超出了精度范围,导致在计算机内部,以9007199254740992的形式储存。

9007199254740993 === 9007199254740992

// true

 

 

所以,如果只验证运算结果是否为安全整数,很可能得到错误结果。下面的函数可以同时验证两个运算数和运算结果:

function trusty (left, right, result) {

  if (

    Number.isSafeInteger(left) &&

    Number.isSafeInteger(right) &&

    Number.isSafeInteger(result)

  ) {

    return result;

  }

  throw new RangeError('Operation cannot be trusted!');

}

 

trusty(9007199254740993, 990, 9007199254740993 - 990)

// RangeError: Operation cannot be trusted!

 

trusty(1, 2, 3)

// 3

Math对象的扩展

概念

ES6在Math对象上新增了17个与数学相关的方法,所有这些方法都是静态方法,只能在Math对象上调用;

Math.trunc()

Math.trunc方法用于去除一个数的小数部分,返回整数部分。

Math.trunc(4.1) // 4

Math.trunc(4.9) // 4

Math.trunc(-4.1) // -4

Math.trunc(-4.9) // -4

Math.trunc(-0.1234) // -0

 

 

对于非数值,Math.trunc内部使用Number方法将其先转为数值

Math.trunc('123.456') // 123

Math.trunc(true) //1

Math.trunc(false) // 0

Math.trunc(null) // 0

 

 

 

对于空值和无法截取整数的值,返回NaN。

Math.trunc(NaN);      // NaN

Math.trunc('foo');    // NaN

Math.trunc();         // NaN

Math.trunc(undefined) // NaN

Math.sign()

Math.sign方法用来判断一个数到底是正数、负数、还是零。对于非数值,会先将其转换为数值。它会返回五种值。

①参数为正数,返回+1;

②参数为负数,返回-1;

③参数为 0,返回0;

④参数为-0,返回-0;

⑤其他值,返回NaN。

 

 

Math.sign(-5) // -1

Math.sign(5) // +1

Math.sign(0) // +0

Math.sign(-0) // -0

Math.sign(NaN) // NaN

 

 

如果参数是非数值,会自动转为数值。对于那些无法转为数值的值,会返回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

Math.cbrt()

Math.cbrt()方法用于计算一个数的立方根。

Math.cbrt(-1) // -1

Math.cbrt(0)  // 0

Math.cbrt(1)  // 1

Math.cbrt(2)  // 1.2599210498948732

 

 

对于非数值,Math.cbrt()方法内部也是先使用Number()方法将其转为数值

Math.cbrt('8') // 2

Math.cbrt('hello') // NaN

Math.clz32()

Math.clz32()方法将参数转为 32 位无符号整数的形式,然后返回这个 32 位值里面有多少个前导 0

Math.clz32(0) // 32

Math.clz32(1) // 31

Math.clz32(1000) // 22

Math.clz32(0b01000000000000000000000000000000) // 1

Math.clz32(0b00100000000000000000000000000000) // 2

上面代码中,0 的二进制形式全为 0,所以有 32 个前导 0;1 的二进制形式是0b1,只占 1 位,所以 32 位之中有 31 个前导 0;1000 的二进制形式是0b1111101000,一共有 10 位,所以 32 位之中有 22 个前导 0。

 

 

左移运算符(<<)与Math.clz32方法直接相关

Math.clz32(0) // 32

Math.clz32(1) // 31

Math.clz32(1 << 1) // 30

Math.clz32(1 << 2) // 29

Math.clz32(1 << 29) // 2

 

 

 

对于小数,Math.clz32方法只考虑整数部分

Math.clz32(3.2) // 30

Math.clz32(3.9) // 30

 

 

对于空值或其他类型的值,Math.clz32方法会将它们先转为数值,然后再计算

Math.clz32() // 32

Math.clz32(NaN) // 32

Math.clz32(Infinity) // 32

Math.clz32(null) // 32

Math.clz32('foo') // 32

Math.clz32([]) // 32

Math.clz32({}) // 32

Math.clz32(true) // 31

Math.imul()

Math.imul方法返回两个数以 32 位带符号整数形式相乘的结果,返回的也是一个 32 位的带符号整数。

Math.imul(2, 4)   // 8

Math.imul(-1, 8)  // -8

Math.imul(-2, -2) // 4

 

 

如果只考虑最后 32 位,大多数情况下,Math.imul(a, b)与a * b的结果是相同的,即该方法等同于(a * b)|0的效果(超过 32 位的部分溢出)。之所以需要部署这个方法,是因为 JS有精度限制,超过 2 的 53 次方的值无法精确表示。这就是说,对于那些很大的数的乘法,低位数值往往都是不精确的,Math.imul方法可以返回正确的低位数值。

Math.fround()

Math.fround方法返回一个数的32位单精度浮点数形式。

对于32位单精度格式来说,数值精度是24个二进制位(1 位隐藏位与 23 位有效位),所以对于 -224 至 224 之间的整数(不含两个端点),返回结果与参数本身一致。

Math.fround(0)   // 0

Math.fround(1)   // 1

Math.fround(2 ** 24 - 1)   // 16777215

 

 

如果参数的绝对值大于 224,返回的结果便开始丢失精度。

Math.fround(2 ** 24)       // 16777216

Math.fround(2 ** 24 + 1)   // 16777216

 

 

Math.fround方法的主要作用,是将64位双精度浮点数转为32位单精度浮点数。如果小数的精度超过24个二进制位,返回值就会不同于原值,否则返回值不变(即与64位双精度值一致)。

// 未丢失有效精度

Math.fround(1.125) // 1.125

Math.fround(7.25)  // 7.25

 

// 丢失精度

Math.fround(0.3)   // 0.30000001192092896

Math.fround(0.7)   // 0.699999988079071

Math.fround(1.0000000123) // 1

 

 

对于 NaN 和 Infinity,此方法返回原值。对于其它类型的非数值,Math.fround 方法会先将其转为数值,再返回单精度浮点数。

Math.fround(NaN)      // NaN

Math.fround(Infinity) // Infinity

 

Math.fround('5')      // 5

Math.fround(true)     // 1

Math.fround(null)     // 0

Math.fround([])       // 0

Math.fround({})       // NaN

Math.hypot()

Math.hypot方法返回所有参数的平方和的平方根

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

 

如果参数不是数值,Math.hypot方法会将其转为数值。只要有一个参数无法转为数值,就会返回 NaN。

Math.expm1()

Math.expm1(x)返回 ex - 1,即Math.exp(x) - 1。

Math.expm1(-1) // -0.6321205588285577

Math.expm1(0)  // 0

Math.expm1(1)  // 1.718281828459045

Math.log1p()

Math.log1p(x)方法返回1 + x的自然对数,即Math.log(1 + x)。如果x小于-1,返回NaN。

Math.log1p(1)  // 0.6931471805599453

Math.log1p(0)  // 0

Math.log1p(-1) // -Infinity

Math.log1p(-2) // NaN

Math.log10()

Math.log10(x)返回以 10 为底的x的对数。如果x小于 0,则返回 NaN。

Math.log10(2)      // 0.3010299956639812

Math.log10(1)      // 0

Math.log10(0)      // -Infinity

Math.log10(-2)     // NaN

Math.log10(100000) // 5

Math.log2()

Math.log2(x)返回以 2 为底的x的对数。如果x小于 0,则返回 NaN。

Math.log2(3)       // 1.584962500721156

Math.log2(2)       // 1

Math.log2(1)       // 0

Math.log2(0)       // -Infinity

Math.log2(-2)      // NaN

Math.log2(1024)    // 10

Math.log2(1 << 29) // 29

双曲函数方法

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)

指数运算符

概念

ES2016新增了一个指数运算符(**);

案例讲解/注意事项

2 ** 2 // 4

2 ** 3 // 8

 

 

这个运算符的一个特点是右结合,而不是常见的左结合。多个指数运算符连用时,是从最右边开始计算的。

// 相当于 2 ** (3 ** 2)

2 ** 3 ** 2

// 512

 

 

指数运算符可以与等号结合,形成一个新的赋值运算符(**=)。

let a = 1.5;

a **= 2;

// 等同于 a = a * a;

 

let b = 4;

b **= 3;

// 等同于 b = b * b * b;

BigInt数据类型

概念

JS所有数字都保存成64位浮点数,这给数值的表示带来了2个限制:

①数值的精度只能到 53 个二进制位(相当于16个十进制位),大于这个范围的整数,JS是无法精确表示的,这使得JS不适合进行科学和金融方面的精确计算;

②大于或等于2的1024次方的数值,JS无法表示,会返回Infinity;

为了解决这个问题,ES2020 引入了一种新的数据类型 BigInt(大整数)来解决这个问题,这是ECMAScript的第8种数据类型;BigInt只用来表示整数,没有位数的限制,任何位数的整数都可以精确表示;

案例讲解/注意事项

const a = 2172141653n;

const b = 15346349309n;

// BigInt 可以保持精度

a * b // 33334444555566667777n

// 普通整数无法保持精度

Number(a) * Number(b) // 33334444555566670000

 

 

为了与 Number 类型区别,BigInt 类型的数据必须添加后缀n

1234 // 普通整数

1234n // BigInt

// BigInt 的运算

1n + 2n // 3n

 

 

BigInt 同样可以使用各种进制表示,都要加上后缀n

0b1101n // 二进制

0o777n // 八进制

0xFFn // 十六进制

 

 

BigInt与普通整数是两种值,它们之间并不相等

42n === 42 // false

 

 

typeof运算符对于 BigInt 类型的数据返回bigint。

typeof 123n // 'bigint'

 

 

BigInt 可以使用负号(-),但是不能使用正号(+),因为会与 asm.js 冲突。

-42n // 正确

+42n // 报错

 

 

JS以前不能计算70的阶乘(即70!),因为超出了可以表示的精度。

let p = 1n;

for (let i = 1n; i <= 70n; i++) {

  p *= i;

}

console.log(p); // 11978571...00000000n

BigInt对象

概念

JS原生提供BigInt对象,可以用作构造函数生成 BigInt 类型的数值,转换规则基本与Number()一致,将其他类型的值转为 BigInt;

BigInt()构造函数必须有参数,而且参数必须可以正常转为数值,并且参数如果是小数,则会报错;

 

BigInt 对象继承了 Object 对象的两个实例方法:

BigInt.prototype.toString()

BigInt.prototype.valueOf()

 

它还继承了 Number 对象的一个实例方法:

BigInt.prototype.toLocaleString()

 

此外,还提供了三个静态方法:

①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。

案例讲解/注意事项

BigInt(123) // 123n

BigInt('123') // 123n

BigInt(false) // 0n

BigInt(true) // 1n

 

 

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

BigInt.asIntN(64, max)

// 9223372036854775807n

BigInt.asIntN(64, max + 1n)

// -9223372036854775808n

BigInt.asUintN(64, max + 1n)

// 9223372036854775808n

max是64位带符号的 BigInt 所能表示的最大值。如果对这个值加1n,BigInt.asIntN()将会返回一个负值,因为这时新增的一位将被解释为符号位。而BigInt.asUintN()方法由于不存在符号位,所以可以正确返回结果;

 

 

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

BigInt.asIntN(32, max) // -1n

BigInt.asUintN(32, max) // 4294967295n

如果BigInt.asIntN()和BigInt.asUintN()指定的位数,小于数值本身的位数,那么头部的位将被舍弃;max是一个64位的 BigInt,如果转为32位,前面的32位都会被舍弃。

 

 

// Number.parseInt() 与 BigInt.parseInt() 的对比

Number.parseInt('9007199254740993', 10)

// 9007199254740992

BigInt.parseInt('9007199254740993', 10)

// 9007199254740993n

由于有效数字超出了最大限度,Number.parseInt方法返回的结果是不精确的,而BigInt.parseInt方法正确返回了对应的 BigInt;对于二进制数组,BigInt 新增了两个类型BigUint64Array和BigInt64Array,这两种数据类型返回的都是64位 BigInt。DataView对象的实例方法DataView.prototype.getBigInt64()和DataView.prototype.getBigUint64(),返回的也是 BigInt;

其他运算

可以使用Boolean()、Number()和String()这三个方法,将 BigInt 可以转为布尔值、数值和字符串类型:

Boolean(0n) // false

Boolean(1n) // true

Number(1n)  // 1

String(1n)  // "1"

上面代码中,注意最后一个例子,转为字符串时后缀n会消失。

另外,取反运算符(!)也可以将 BigInt 转为布尔值。

!0n // true

!1n // false

 

 

数学运算

数学运算方面,BigInt 类型的+、-、*和**这四个二元运算符,与 Number 类型的行为一致。除法运算/会舍去小数部分,返回一个整数;

几乎所有的数值运算符都可以用在 BigInt,但是有两个例外。

①不带符号的右移位运算符>>>

②一元的求正运算符+

>>>运算符是不带符号的,但是 BigInt总是带有符号的,导致该运算无意义,完全等同于右移运算符>>。后者是因为一元运算符+在 asm.js 里面总是返回 Number 类型,为了不破坏 asm.js 就规定+1n会报错。

 

BigInt 不能与普通数值进行混合运算。

1n + 1.3 // 报错

 

(2n**53n + 1n) + 0.5这个表达式,如果返回 BigInt 类型,0.5这个小数部分会丢失;如果返回 Number 类型,有效精度只能保持 53 位,导致精度下降。

同样的原因,如果一个标准库函数的参数预期是 Number 类型,但是得到的是一个 BigInt,就会报错。

// 错误的写法

Math.sqrt(4n) // 报错

// 正确的写法

Math.sqrt(Number(4n)) // 2

上面代码中,Math.sqrt的参数预期是 Number 类型,如果是 BigInt 就会报错,必须先用Number方法转一下类型,才能进行计算。

 

 

BigInt 对应的布尔值,与 Number 类型一致,即0n会转为false,其他值转为true。

if (0n) {

  console.log('if');

} else {

  console.log('else');

}

// else

上面代码中,0n对应false,所以会进入else子句。

 

 

比较运算符(比如>)和相等运算符(==)允许 BigInt 与其他类型的值混合计算,因为这样做不会损失精度。

0n < 1 // true

0n < true // true

0n == 0 // true

0n == false // true

0n === 0 // false

 

 

BigInt 与字符串混合运算时,会先转为字符串,再进行运算。

'' + 123n // "123"

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值