数值的扩展

文章目录

  • 数值的扩展
    • 1.二进制和八进制表示法
    • 2.Number.isFinite()、Number.isNaN()
    • 3.Number.parseInt()、Number.parseFloat();
    • 4.Number.isInteger()
    • 5. Number.EPSILON
    • 6. 安全整数和Number.isSafeInteger()
    • 7. Math对象的扩展
      • Math.trunc()
      • Math.sign()
      • Math.cbrt()
      • Math.clz32()
      • Math.imul()
      • Math.fround()
      • Math.hypot()
      • 对数方法
    • 8.指数运算符
    • 9.BigInt数据类型
      • BigInt对象
      • 转换规则
      • 数学运算
      • 其他运算

数值的扩展

1.二进制和八进制表示法

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

    0b111110111 === 503 // true
    00767 === 503 //true

从ES5开始,在严格模式中,八进制就不再允许使用前缀0表示,ES6进一步明确,要使用前缀0o表示。

    // 非严格模式
    (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

2.Number.isFinite()、Number.isNaN()

ES6在Number对象上,新提供了Number.isFinite()和Number.isNaN()两个方法。

Number.isFinite()用来检查一个数值是否为有限的(finite),即不是Infinity。

    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.isFinite一律返回false。

Number.isNaN()用来检查一个值是否为NaN。

    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

如果参数类型不是NaN,Number.isNaN一律返回false。

它们与传统的全局方法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

3.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

4.Number.isInteger()

Number.isInteger()用来判断一个数值是否为整数。

    Number.isInteger(25) // true
    Number.isInteger(25.1) // false

JavaScript内部,整数和浮点数采用的是同样的存储方法,所以25和25.0被视为同一个值。

    Number.isInteger(25) // true
    Number.isInteger(25.0) // true

如果参数不是数值,Number.isInteger返回false。

    Number.isInteger() // false
    Number.isInteger(null) // false
    Number.isInteger('15') // false
    Number.isInteger(true) // false

注意,由于JavaScript采用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),即小于JavaScript能够辨别的最小值,会被自动转为0。这时Number.isInteger也会误判。

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

上面代码中,5E-325由于值太小,会被自动转为0,因此返回true。

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

5. Number.EPSILON

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

6. 安全整数和Number.isSafeInteger()

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

7. 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') // 1233
    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.trunc = Math.trunc || function(x) {
        return x < 0 ? Math.ceil(x) : Math.floor(x);
    }

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.sign = Math.sign || function(x) {
           x = +x; // convert to a number
           if(x === 0 || isNaN(x)){
               return x;
           }
           return x > 0 ? 1 : -1;
       }

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.cbrt = Math.cbrt || function(x) {
        var y = Math.pow(Math.abs(x), 1/3);
        return x < 0 ? -y : y;
    }

Math.clz32()

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

Math.imul()

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

Math.fround()

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

Math.hypot()

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

对数方法

ES6新增了4个对数相关方法。
(1) Math.expm1()

(2) Math.log1p()

(3) Math.log10()

(4) Math.log2()

8.指数运算符

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

9.BigInt数据类型

简介

JavaScript所有数字都保存成64位浮点,这给数值的表示带来了两个限制。一是数值的精度只能到53个二进制位(相当于16个十进制位),大于这个范围的整数,JavaScript是无法精确的表示的,这使得JavaScipt不适合进行科学和金融方面的精确计算。二是大于或等于2的1024次方的数值,JavaScript无法表示,会返回Infinity。

    // 超过53个二进制位的数值,无法保持精度
    Math.pow(2, 53) === Math.pow(2, 53) + 1 //true

    // 超过2的1024次方的数值,无法表示
    Math.pow(2, 1024) //Infinity

ES2020引入了一种新的数据类型BigInt(大整数),来解决这个问题,这是ECMAScript的第八种数据类型。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

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

    typeof 123n // 'bigint'

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

    -42n // 正确
    +42n // 错误

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

    let p = 1;
    for (let i = 1; i <= 70; i++) {
    p *= i;
    }
    console.log(p); // 1.197857166996989e+100

现在支持大整数了,就可以算了,浏览器的开发者工具运行下面代码,就OK。

    let p = 1n;
    for (let i = 1n; i <= 70n; i++) {
    p *= i;
    }
    console.log(p); // 11978571...00000000n

BigInt对象

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

    BigInt(123) // 123n
    BigInt('123') // 123n
    BigInt(false) // 0n
    BigInt(true) // 1n

BigInt()构造函数必须有参数,而且参数必须可以正常转为数值,下面的用法都会报错。

    new BigInt() //TypeError
    BigInt(undefined) // TypeError
    BigInt(null) // TypeError
    BigInt('123n') // SyntaxError
    BigInt('abc') // SyntaxError

上面代码中,尤其值得注意字符串123n无法解析成Number类型,所以会报错。

参数如果是小数,也会报错。

    BigInt(1.5) // RangeError
    BigInt('1.5') // SyntaxError

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

  • BigInt.prototype.toString()
  • BigInt.prototype.valueOf()

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

  • BigInt.prototype.toLocaleString()

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

  • BigInt.asIntN(width, BigInt); 给定的BigInt转为0到2width - 1 之间对应的值。
  • BigInt.asIntN(width, BigInt); 给定的BigInt转为-2width-1 到 2width -1 -1之间对应的值。
  • BigInt.parseInt(string[, radix]):近似于Number.parseInt(),将一个字符串转换成指定进制的 BigInt。
    const max = 2n ** (64n - 1n) - 1n;

    BigInt.asIntN(64, max)
    // 9223372036854775807n
    BigInt.asIntN(64, max + 1n)
    // -9223372036854775808n
    BigInt.asUintN(64, max + 1n)
    // 9223372036854775808n

未完待续…

转换规则

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

    Boolean(0n) // false
    Boolean(1n) // true
    Number(1n) // 1
    String(1n) // "1"

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

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

    !0n // true
    !1n // false

数学运算

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

    9n / 5n // 1n

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

  • 不带符号的右移位运算符>>>
  • 一元的求正运算符+

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

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

    1n + 1.3 // 报错

上面代码报错是因为无论返回的是BigInt或Number,都会导致丢失精度信息。比如(2n**53n + 1n) + 0.5这个表达式,如果返回BigInt类型,0.5这个小数部分会丢失;如果返回Number类型,有效精度只能保持53位,导致精度下降。

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

    // 错误的写法
    Math.sqrt(4n) // 报错

    // 正确的写法
    Math.sqrt(Number(4n)) // 2

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

asm.js里面,|0跟在一个数值的后面会返回一个32位整数。根据不能与Number类型混合运算的规则,BigInt如果与|0进行运算会报错。

    1n | 0 // 报错

其他运算

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"

你可能感兴趣的:(ES6)