Javascript中Number类型详解

440 1 年前
在 JavaScript 中,Number类型是一种基本的数据类型。JavaScript 还支持 Number 对象,该对象是原始数值的包装对象。在必要时,JavaScript 会自动地在原始数据和对象之间转换。

ECMAScript 使用 IEEE754 格式来表示整数和浮点数值(浮点数值在某些语言中也被称为双精度数值)。

ECMAScript 内部,所有数字都是以64位浮点数形式储存,即使整数也是如此。所以,11.0是相同的,是同一个数。

Javascript 中有几种不同的数值字面量格式

  • 十进制:没有前导0的数值。
  • 八进制:有前缀0o或0O的数值(ES5 开始,在严格模式下八进制不再允许使用前缀0表示)。
  • 十六进制:有前缀0x或0X的数值。
  • 二进制:有前缀0b或0B的数值。

浮点数值(整数和浮点数)

所谓浮点数值,就是该数值中必须包含一个小数点,并且小数点后面必须至少有一位数字。虽然小数点前面可以没有整数,但我们不推荐这种写法。

var num1 = 1.1;
var num2 = 0.1;
var num3 = .1; // 有效,但不推荐

ECMAScript 语言的底层根本没有整数,所有数字都是小数(64位浮点数)。某些运算只有整数才能完成,此时 JavaScript 会自动把64位浮点数,转成32位整数,然后再进行运算

保存浮点数值需要的内存空间是保存整数值的两倍,因此 ECMAScript 自身会在恰当的时机将浮点数值转换为整数值。

  • 小数点后面没有跟任何数字,那么这个数值就可以作为整数值来保存
  • 浮点数值本身表示的就是一个整数(如 1.0),那么该值也会被转换为整数:
var floatNum1 = 1.; // 小数点后面没有数字——解析为 1
var floatNum2 = 10.0; // 整数——解析为 10

于那些极大或极小的数值,可以用 e 表示法(即科学计数法)表示的浮点数值表示。ECMAScript 会将那些小数点后面带有 6 个零以上的浮点数值转换为以 e 表示法 表示的数值(例如,0.0000003 会被转换成 3e-7)。

浮点数值的最高精度是 17 位小数,进行算术计算时其精确度远远不如整数,例如 0.1 加 0.2的结果不是 0.3,而是 0.30000000000000004。

//0.22999999999999998
//
console.log(0.11 + 0.12)

浮点数的存储

JavaScript 中所有数字包括整数小数都只有一种类型 — Number。它的实现遵循 IEEE 754 标准,使用 64 位固定长度来表示,也就是标准的 double 双精度浮点数。这样的存储结构优点是可以归一化处理整数和小数,节省存储空间。

64位比特又可分为三个部分:

  • 符号位S:第 1 位是正负数符号位(sign),0代表正数,1代表负数
  • 指数位E:中间的 11 位存储指数(exponent),用来表示次方数
  • 尾数位M:最后的 52 位是尾数(mantissa),超出的部分自动进一舍零

符号位决定了一个数的正负,指数位决定了数值的大小,小数位决定了数值的精度。

实际数字就可以用以下公式来计算:

以上的公式遵循科学计数法的规范,在十进制时为0<M<10,到二进制时0<M<2。Javascript数值存储为二进制时整数部分只能是1,所以可以被舍去,只保留后面的小数部分。如 4.5 转换成二进制就是 100.1,科学计数法表示是 1.001*2^2,舍去1后 M = 001E是一个无符号整数,因为长度是11位,取值范围是 0~2047 (2的11次方减1)。但是科学计数法中的指数是可以为负数的,所以要分一半表示负数,取一个中间值 1023 为正负指数的分隔点,实际指数为 E-1023 表示为正。如4.5 上面得到实际指数为 2 ,因此指数 E = 2+1023 为 1025,尾数M为 001。

1025转换为二进制为10000000001,故4.5的二进制存储格式:

最终的公式变成:

(M为小数部分,故在算实际值是M的值为0.M,V是一个二进制值)

以上公式为一个数在指数部分不为0和2047时内部实际的表示形式

  • E不全为0或不全为1(不为00000000000和11111111111):以上公式为一个数内部实际的表示形式
  • E全为0:浮点数的指数E等于0-1023,有效数字M不再加上第一位的1,而是为0.xxxxxx的小数。这样做是为了表示±0,以及接近于0的很小的数字。
  • E全为1:这时,如果有效数字M全为0,表示±无穷大(正负取决于符号位s);如果有效数字M不全为0,表示这个数不是一个数(NaN)。

数值精度与范围

数值的精度和范围是两个概念,精度即Javascript能准确处理数值的范围。当一个数值与存储二进制格式一对一时表示这个数值在精度内。

数值范围表示Javascript数值能表示的数值大小区间。

浮点数的存储一节中我们知道,尾数位M最多有53个二进制位,因此一个数转换为二进制后尾数位小于等于53的数都是精确的,超过53位的,Javascript 会进一舍零为53位从而就不精确了。53位尾数二进值最大值为1.1....11小数点后52个 1,带上符号位,Javascript整数的精度范围是:-(2^53-1)2^53-1

指数有11位,指数最大值二进制为 11111111111 转换为十进制为2^11-1,故指数最大为 2047。从前面我们了解到,当E全为1,表示无穷大NaN。所以对于Javascript能表示的数值最大值的指数为2046(11111111110),尾数为1.111....(小数点后52个1),实际值为:1.79769313486231570814527423732E308,即:2^(2047-1023)-1 = 2^1024 - 1

如果某次计算的结果得到了一个超出 JavaScript 数值范围的值,那么这个数值将被自动转换成特殊的 Infinity 值。具体来说,如果这个数值是负数,则会被转换成-Infinity(负无穷),如果这个数值是正数,则会被转换成 Infinity(正无穷)。

11位指数最小是全为0时,指数E等于0-1023,有效数字M不再加上第一位的1,而是还原为0.xxxxxx的小数(小数点后52位)。

此时实际值为:4.94065645841246544176568792868E-324 约等于 5E-324,即 2^(-1023-52) = 2^-1075

//5e-324
Math.pow(2, -1075)

JavaScript 提供Number对象的MAX_VALUEMIN_VALUE属性,返回可以表示的具体的最大值和最小值

Number.MAX_VALUE =2^1024 - 1 = 1.7976931348623157e+308
Number.MIN_VALUE =  2^-1075 = 5e-324

要想确定一个数值是不是有穷的(换句话说,是不是位于最小和最大的数值之间),可以使用 isFinite()函数。这个函数在参数位于最小与最大数值之间时会返回 true

ES6Number对象上面,新增一个极小的常量Number.EPSILON。根据规格,它表示 1 与大于 1 的最小浮点数之间的差。这两个数最小差其实就是尾数未尾相差1,即2^-52等于2.220446049250313e-16

NaN

前面我们说到‘E全为1时如果有效数字M全为0,表示±无穷大(正负取决于符号位s);如果有效数字M不全为0,表示这个数不是一个数(NaN)

NaN,即非数值(Not a Number)是一个特殊的数值,这个数值用于表示一个本来要返回数值的操作数 未返回数值的情况(这样就不会抛出错误了)。任何涉及 NaN 的操作(例如 NaN/10)都会返回 NaN, 0 除以 0 会返回 NaN,正数除以 0 返回 Infinity,负数除以 0 返回-Infinity,NaN 与任何值都不相等,包括 NaN 本身。

ECMAScript 定义了 isNaN()函数,在接收到一个值之后,会尝试将这个值转换为数值。某些不是数值的值会直接转换为数值,例如字符串"10"或 Boolean 值。而任何不能被转换为数值的值都会导致这个函数返回 true

isNaN()也适用于对象。在基于对象调用 isNaN() 函数时,会首先调用对象的 valueOf()方法,然后确定该方法返回的值是否可以转换为数值(原始类型的值)。如果不能,再调用 toString()方法,再测试返回值。

数值转换

有 4 个函数可以把非数值转换为数值:Number()isNaN()parseInt()parseFloat()isNaN()内部参数的转换是基于Number(),在Javasctip中的数学运算也会涉及到数值转换。

Number()函数转换规则如下:

  • Boolean 值 truefalse 将分别被转换为 10
  • 如果是数字值,只是简单的传入和返回
  • null ,转换为 0
  • undefined 转换为 NaN
  • 字符串转换的情况
  • 如果字符串中只包含数字(包括前面带正号或负号的情况),则将其转换为十进制数值,即"1"会变成 1,"123"会变成 123,而"011"会变成 11(注意:前导的零被忽略了);
  • 如果字符串中包含有效的浮点格式,如"1.1",则将其转换为对应的浮点数值(同样,也会忽略前导零);
  • 如果字符串中包含有效的十六进制格式,例如"0xf",则将其转换为相同大小的十进制整数值;
  • 如果字符串是空的(不包含任何字符),则将其转换为 0;
  • 如果字符串中包含除上述格式之外的字符,则将其转换为 NaN。
  • 对象转换的情况,包含单个数值的数组返回这个数值,空数组返回0,其余规模则如下:
  • 第一步,调用对象自身的valueOf()方法。如果返回原始类型的值,则直接对该值使用Number()函数,不再进行后续步骤。
  • 第二步,如果valueOf()方法返回的还是对象,则改为调用对象自身的toString()方法。如果toString()方法返回原始类型的值,则对该值使用Number()函数,不再进行后续步骤。
  • 第三步,如果toString()方法返回的是对象,就报错。
var num1 = Number("Hello world!"); //NaN
var num2 = Number(""); //0
var num3 = Number("000011"); //11
var num4 = Number(true); //1
rue -> 1  false -> 0
"" -> 0  "12" -> 12  "12px" -> NaN
null -> 0
undefined -> NaN
[] -> 0
[12] -> 12
{} 、 /^$/ 、 function(){} -> NaN
var obj = {x: 1};
Number(obj) // NaN

// 等同于
if (typeof obj.valueOf() === 'object') {
  Number(obj.toString());
} else {
  Number(obj.valueOf());
}
var obj = {
  valueOf: function () {
    return {};
  },
  toString: function () {
    return {};
  }
};

Number(obj)
// TypeError: Cannot convert object to primitive value

Number({
  valueOf: function () {
    return 2;
  }
})
// 2

Number({
  toString: function () {
    return 3;
  }
})
// 3

Number({
  valueOf: function () {
    return 2;
  },
  toString: function () {
    return 3;
  }
})
// 2

parseInt()函数转换规则如下:

  • parseInt()函数在转换字符串时会忽略字符串前面的空格,直至找到第一个非空格字符。如果第一个字符不是数字字符或者负号,parseInt()`` 就会返回NaN`;
  • 如果第一个字符是数字字符parseInt()会继续解析第二个字符,直到解析完所有后续字符或者遇到了一个非数字字符。例如,1234blue会被转换为 1234,因为blue会被完全忽略。类似地,22.5会被转换为 22,因为小数点并不是有效的数字字符。
  • 如果字符串中的第一个字符是数字字符,parseInt()也能够识别出各种整数格式
var num1 = parseInt("1234blue"); // 1234
var num2 = parseInt(""); // NaN
var num3 = parseInt("0xA"); // 10(十六进制数)
var num4 = parseInt(22.5); // 22
var num5 = parseInt("070"); // 56(八进制数)
var num6 = parseInt("70"); // 70(十进制数)
var num7 = parseInt("0xf"); // 15(十六进制数)
parseInt()函数提供第二个参数:转换 时使用的基数(即多少进制)。
var num1 = parseInt("AF", 16); //175
var num2 = parseInt("AF"); //NaN
var num1 = parseInt("10", 2); //2 (按二进制解析)
var num2 = parseInt("10", 8); //8 (按八进制解析)
var num3 = parseInt("10", 10); //10 (按十进制解析)
var num4 = parseInt("10", 16); //16 (按十六进制解析)

parseFloat()函数转换规则如下:

  • parseFloat()类似`parseInt()``,从第一个字符(位置 0)开始解析每个字符。而且也是一直解析到字符串末尾,或者解析到遇见一个无效的浮点数字字符为止。
  • 字符串中的第一个小数点是有效的,而第二个小数点就是无效的了
  • parseFloat()只解析十进制值
var num1 = parseFloat("1234blue"); //1234 (整数)
var num2 = parseFloat("0xA"); //0
var num3 = parseFloat("22.5"); //22.5
var num4 = parseFloat("22.34.5"); //22.34
var num5 = parseFloat("0908.5"); //908.5
var num6 = parseFloat("3.125e7"); //31250000

isNaN()

用来判断一个值是否为NaN。isNaN()只对数值有效,如果传入其他值,会被先转成数值,参数转换规则为 Number()

isNaN('Hello') // true
// 相当于
isNaN(Number('Hello')) // true

isNaN({}) // true
//相当于
isNaN(Number({})) // true

isNaN(['xzy']) // true
// 等同于
isNaN(Number(['xzy'])) // true

对于空数组和只有一个数值成员的数组,isNaN()返回false

isNaN([]) // false
isNaN([123]) // false
isNaN(['123']) // false

因为一个一个不是NaN的值通过isNaN()返回可能为 true,故要正确的判断是否为NaN得先判断这个是不是一个数值或一个值不等于他自己

//NaN本身就是数字
function myIsNaN(value) {
      return typeof value === 'number' && isNaN(value);
 }
//NaN不等于NaN
function myIsNaN(value) {
  return value !== value;
}

Javasctip中的数学运算

Javasctip中的数学运算有 +,-,*,/ ,除了加法( + )有特殊性,其余的运算符都是数学运算,也就是遇到非数字类型,需要把其转换为 Number 再进行运算。

加法的特殊性:在遇到字符串的时候, + 不是数学运算,是字符串拼接,只要不遇到字符串就是数学运算

1-"1" -> 0
10*null -> 0
10/undefined -> NaN  //Number(undefined)==>NaN
10*[10] -> 100

1+"1" -> "11"
null+"1" -> null1
分类栏目
© 2018邮箱:11407215#qq.comGitHub沪ICP备12039518号-6