ECMAScript 使用 IEEE754
格式来表示整数和浮点数值(浮点数值在某些语言中也被称为双精度数值)。
ECMAScript 内部,所有数字都是以64位浮点数形式储存,即使整数也是如此。所以,1
与1.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 = 001
。E
是一个无符号整数,因为长度是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_VALUE
和MIN_VALUE
属性,返回可以表示的具体的最大值和最小值
Number.MAX_VALUE =2^1024 - 1 = 1.7976931348623157e+308
Number.MIN_VALUE = 2^-1075 = 5e-324
要想确定一个数值是不是有穷的(换句话说,是不是位于最小和最大的数值之间),可以使用 isFinite()
函数。这个函数在参数位于最小与最大数值之间时会返回 true
,
ES6
在Number
对象上面,新增一个极小的常量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 值
true
和false
将分别被转换为1
和0
。 - 如果是数字值,只是简单的传入和返回
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