【JavaScript】基本数据类型:Number

123 阅读9分钟

Number 是什么

是一个使用双精度64位二进制格式(IEEE754)表示的 浮点值

IEEE 754 双精度浮点数

组成

IEEE754双精度浮点数 在内存中使用 64 bits 来表示 3 个部分:

1.符号位(sign)
(1)占用 1 bit;
(2)符号位有 0 和 1 两个值:0 表示(1)0(-1)^{0},为正数;1 表示(1)1(-1)^{1},为负数。

2.指数(阶码)位(exponent)
(1)占用 11 bits;
(2)指数位可表示的最大值为2112^{11} - 1 = 2047;最小值为 0 。
(3)内存中指数位的值并非真值,而是通过实际指数值和偏移量(中间值)相加而得。由于指数表示的是浮点数的小数 点位置,左移为正,右移为负。因此如果想在只有正数的情况下表示正负,则需要引入一个中间值作为实际值偏移的标准,也就是偏移量。通过取2047的中间值作为偏移量,偏移量 = 2e112^{e-1}-1 ,这里 e 表示指数位数 ,得偏移量为1023。通过偏移量,指数位做到了表示正负指数。大于偏移量则为正数;小于偏移量则为负数。得指数真值的范围在 [ -1023,1024 ] 之间。

3. 尾数位(mantissa)
(1)占用 52 bits;
(2)表示 52 个有效数值。

内存存储如图所示:
216_555_0fe.png

计算

一个浮点数的表示方式有很多,但规范中一般使用科学计数法,例如在十进制中 1234 用1.2341031.234*10^{3},而不用12.3410212.34*10^{2}0.12341040.1234*10^{4}。二进制中只有0与1,那么按科学计数法,首位只可能是1,对此IEEE 754省略了这个默认的 1。对于首位为 1 的二进制科学计数法称为规格化。

规格化计算公式: (1)sign(1+mantissa)2exponent1023(-1)^{sign} * (1+mantissa) *2^{exponent - 1023}

规格化浮点数的计算,即在进行计算前,一个隐藏的首位 1 被添加到尾数位mantissa上,这样可以使尾数的最高位始终为 1。

为了增加浮点数的表示能力,标准规定,在指数位exponent为 0 且mantissa不为 0 时,尾数位 mantissa 不再必须添加隐藏的首位 1,而是直接用二进制小数的形式表示,对应的浮点数值的计算公式变为:

非规格化计算公式: (1)signmantissa21023(-1)^{sign} * mantissa *2^{- 1023}

根据指数位分出三种情况

 (1) exponent 为 0

  • 尾数mantissa为0:表示浮点0
  • 尾数mantissa不为0:表示非规格化数 = (1)signmantissa21023(-1)^{sign} * mantissa *2^{- 1023}

 (2) exponent 为 2047

  • 尾数mantissa为0:根据sign为0和1可以表示正负无穷大
  • 尾数mantissa不为0:表示NaN(Not a Number)

 (3) exponent 为 [1,2046]

    使用规格化公式(1)sign(1+mantissa)2exponent1023(-1)^{sign} * (1+mantissa) *2^{exponent - 1023}

正无穷数

    (1)0(1+0)220471023(-1)^{0} * (1 + 0) *2^{2047 - 1023} = 210242^{1024}

  =Infinity= Infinity

负无穷数

    (1)1(1+0)220471023(-1)^{1} * (1 + 0) *2^{2047 - 1023} = 21024-2^{1024}

  =Infinity= -Infinity

最大正数

    (1)0(1+1.1111111111)220461023(-1)^{0} * (1 + 1.1111111111) *2^{2046 - 1023}

  =1.7976931348623157e+308  < 21024= 1.7976931348623157e+308  < ≈  2^{1024}

规格化最小正数

    (1)0(1+0.0000...0)201023(-1)^{0} * (1 + 0.0000...0) *2^{0 - 1023} = 210232^{-1023}

  =1.1125369292536007e308= 1.1125369292536007e-308

非规格化最小正数

    (1)0(0.0000...001)201023(-1)^{0} * (0.0000...001) *2^{0 - 1023} = (0.1)25121023(0.1)*2^{-51} * 2^{-1023}

  =5e324= 5e-324

最小负数

    (1)1(1.111...111)220461023(-1)^{1} * (1.111...111) *2^{2046 - 1023}

  =1.7976931348623157e+308<21024= -1.7976931348623157e+308 < ≈ -2^{1024}

规格化最大负数

    (1)1(1+0.0000...0)201023(-1)^{1} * (1 + 0.0000...0) *2^{0 - 1023} = 121023-1*2^{-1023}

  =1.1125369292536007e308= -1.1125369292536007e-308

非规格化最大负数

    (1)1(0.0000...001)201023(-1)^{1} * (0.0000...001) *2^{0 - 1023} = 1(0.1)25121023-1*(0.1)*2^{-51} * 2^{-1023}

  =5e324= -5e-324

Number 的值

+0,-0

由于JavaScript中的Number是根据IEEE 754格式存储在内存中,因此由于符号位sign的存在,0有+0和-0。因此本质上并不相同。

值的比较

// 在相等运算符下,0、-0、+0 视为值相等。
  0 == +0   // true
  0 == -0   // true
 +0 == -0   // true

// 在严格相等运算符下,0、-0、+0 视为值相等。
  0 === +0   // true
  0 === -0   // true
 +0 === -0   // true
 
// Object.is()静态方法下,+0 和 0视为相等,而-0不等于 +0 和 0
 Object.is(0,+0)    // true
 Object.is(0,-0)    // false
 Object.is(+0,-0)   // false

值的判断

// 0、+0 和 -0并非无法区分,由于负号的原因,可通过除法进行判断。
 1 / 0    //  Infinity
 1 /-0    // -Infinity

NaN

全局属性NaN是一个表示非数字的值。根据IEEE754标准可知,NaN在内存中的表示并不唯一,这也解释了为什么严格相等总是不等于自身。

值的比较

const num = NaN;
// 在相等运算符下,NaN不等于任何值包括它自己。
num == num     // false
// 在严格相等运算符下,NaN不等于任何值包括它自己。
num === num    // false
// NaN无法判断大小
num > 0        // false
num < 0        // false
// Object.is()静态方法下,NaN等于NaN。
Object.is(num,num)   // true
Object.is(num,NaN)   // true

值的判断

// 通过isNaN()判断,输入参数会被进行强制转换成数字,如果符合IEEE754标准的NaN则返回true
isNaN(NaN);      // true
isNaN('123a');   // true
isNaN({});       // true
isNaN(undefined) // true
isNaN(null)      // false
isNaN(true);     // false   
isNaN([]);       // false
isNaN('123');    // false

// 通过Number.isNaN()判断,该函数不会对参数进行强制转换,只判断参数是否为NaN
Number.isNaN(NaN);      // true
Number.isNaN('123a');   // false
Number.isNaN({});       // false
Number.isNaN(undefined) // false
Number.isNaN(null)      // false
Number.isNaN(true);     // false   
Number.isNaN([]);       // false
Number.isNaN('123');    // false

Infinity

全局属性 Infinity 是一个数值,表示无穷大。有正无穷大和负无穷大之分,即±Infinity。根据IEEE754规定,无穷大 实际为 ±21024±2^{1024},即JavaScript可表示数的上下限。

Number . MAX_SAFE_INTEGER

静态数据属性表示在 JavaScript 中最大的安全整数(25312^{53}-1)。根据IEEE 754可知,数值在内存中最大有效位数为53位,最大安全整数由此而来。

Number . MIN_VALUE

JavaScript(IEEE754)可以表达的最小正数。

Number . MAX_VALUE

JavaScript(IEEE754)可以表达的最大正数。

进制数

JavaScript中默认使用十进制,但是也可以使用不同进制来表示数字,包括二进制、八进制和十六进制。但JavaScript对进制数有一些要求和限制。

// 二进制
0b111       // 7
0B111       // 7
// 八进制
0o17        // 15
0O17        // 15
017         // 15
// 十六进制
0x1a        // 26
0X1a        // 26
  1. JavaScript中默认使用十进制,因此使用非十进制时,如果不规定输出,进制则会自动转换为十进制。
console.log(0x1a);      // 26
console.log(0x1a + 1);  // 27
console.log(0x1a + '1') // 261
  1. 在使用十六进制时,JavaScript中的字母不区分大小写。例如,0xa和0xA是相同的。
 0xa === 0xA      // true
 Object.is(0xa, 0xA)  // true
  1. 在使用八进制时,如果使用数字8或9,它们将被视为十进制数字。因此,应该注意避免使用数字8和9。
0o19      // Uncaught SyntaxError: Invalid or unexpected token
019       // 19

转换为 Number 的方法

Number( )和 +

Number()将参数强制转换为数值原始值。如果值不能转换,则返回 NaN。
+号遵守和Number()强制转换完全相同的规则,只是+属于隐式转换。

// +号使用示例
+-1     // -1
+NaN    // NaN
+{}     // NaN

下文例子以Number()表示。

// number 类型
Number(-0)         // -0
Number(+0)         // 0
Number(NaN)        // NaN
Number(Infinity)   // Infinity
Number(-Infinity)  // -Infinity
Number(0x1a)       // 26
Number(070)        // 56
// boolean 类型
Number(true)      // 1
Number(false)     // 0
// string 类型
Number('123')      // 123
Number('123a')     // NaN
Number('true')     // NaN
Number('0x1a')     // 26
Number('070')      // 70
Number('0o70')     // 56
Number('0b111')    // 7
// undefined 和 null
Number(undefined)    // NaN
Number(null)         // 0
// 包装类型
Number(new Number())       // 0
Number(new Number(123))    // 123
Number(new Boolean())      // 0
Number(new Boolean(true))  // 1
Number(new String('123'))  // 123
Number(new String('123a')) // NaN
// 对象
Number({})                                // NaN
Number({toString(){return 1}})            // 1
Number({valueOf(){return 1}})             // 1
Number({toString(){return ''}})           // 0
Number({valueOf(){return ''}})            // 0
Number({toString(){return '123'}})        // 123
Number({valueOf(){return '123a'}})        // NaN
Number({valueOf(){return []}})            // NaN
Number({toString(){return []}})           // Cannot convert object to primitive value
Number({valueOf(){return new Number()}})  // NaN
Number({toString(){return new Number()}}) // Cannot convert object to primitive value
// 数组
Number([])         // 0
Number([123])      // 123
Number([123,123])  // NaN

Number()转换对象时遵循三个步骤:

  1. 将需要转化的对象进行 valueOf()操作,如果返回原始类型的值,就直接转化。
  2. 如果 valueOf()返回的是对象,则对其进行 toString()操作。如果toString() 返回原始类型的值,就直接转化。
  3. 如果 toString()返回的是对象,则报错 Cannot convert object to primitive value

parseInt( ) 和 Number.parseInt( )

parseInt() 和 Number.parseInt() 具有一样的函数功能。下文实例用parseInt()代表parseInt和Number.parseInt()。

parseInt === Number.parseInt  // true
// 证明引用同一个函数,Number.parseInt() 是为了模块化的产物。

parseInt()更专注于字符串的整数解析,这是与Number()不同的地方。

Number('123a');    // NaN
parseInt('123a');  // 123

Number('a123');    // NaN
parseInt('a123');  // NaN

Number('1.23');    // 1.23
parseInt('1.23');  // 1

Number('0x1a');    // 26
parseInt('0x1a');  // 26

Number('0x1a');    // 26
parseInt('0x1a');  // 26

Number('070');     // 70
parseInt('070');   // 70  ES5规范不再允许parseInt函数的实现环境把以0字符开始的字符串作为八进制数值。

Number([])         // 0
parseInt([])       // NaN

// parseInt() 判断传入参数是否为字符串或数值,如果不是字符串返回NaN对字符串逐个字符解析,
// 如果第一个字符不是数字则直接返回NaN,
// 如果第一个是数字则解析到非数字为止
// 最后返回十进制数

parseInt()第二个参数可以指定转换的进制,范围在2 到 36 之间的整数 (10 + 26个英文字母)。

parseInt('070',2)   // 0
parseInt('0x1a',2)  // 0

// 在转换数值的时候,如果数值是二进制、八进制、十六进制,会先将其转换为十进制再转换为字符串进行逐个字符分析。
parseInt(1001,2)  // 相当于parseInt(1001,2) 得 9
parseInt(070,2)   // 相当于parseInt(56,2)   得 NaN
parseInt(012,2)   // 相当于parseInt(10,2)   得 2
parseInt(070,8)   // 相当于parseInt(56,8)   得 5*8+6 = 46
parseInt(0x1a,16) // 相当于parseInt(26,16)  得 2*16+6 = 38
parseInt(0x0c,2)  // 相当于parseInt(12,2)   得 1

总结:
如果只有一个参数:parseInt(n)

  1. n是数值,会被转换为10进制整数输出

  2. n是字符串,判断是否以0x或0X开头:如果是,则从0x之后截取0-9,a-f的字符,最后根据十六进制输出十进制结果;如果不是,则截取0-9的字符输出。

如果有两个参数:parseInt(n,r)

  1. 如果n是数值,则会先转换为十进制整数,再转换为字符串,最后根据r的值逐个截取字符,将截取值转换为十进制整数。

  2. 如果n是字符串,则根据r的值逐个截取字符,将截取值转换为十进制整数。(一种情况特殊,如果以0x或者0X开头,当r为16时可以从0x之后开始截取0-9,a-f的字符)。

  3. 截取字符长度为0时输出NaN

parseFloat( ) 和 Number.parseFloat( )

parseFloat() 和 Number.parseFloat() 具有一样的函数功能。下文实例用parseFloat() 代表parseFloat 和 Number.parseFloat()。

parseFloat === Number.parseFloat    // true

parseFloat()只有一个参数,专注于小数的字符串解析。

Number('1.23')        // 1.23
parseFloat('1.23')    // 1.23

Number('1.23a')       // NaN
parseFloat('1.23a')   // 1.23

// parseFloat无法识别十六进制
Number('0x1a')       // 26
parseFloat('0x1a')   // 0

关于 Number 的常见问题

为什么Number的原始值可以使用方法?

在访问数字的属性时,会创建一个Number类型的对象包装器,调用指定的方法,最后销毁了实例,而这一切行为都在JavaScript内部完成。

new Number() 和 Number() 的区别?

使用 new 时,Number将作为构造器使用,创建一个Number 对象,并不是原始值。 不使用new时,Number作为普通函数调用,它将参数强制转换为数值原始值。