# 数值表示
# 1.整数和浮点数
JavaScript 内部,所有数字都是以64位浮点数形式储存,即使整数也是如此。所以,1
与1.0
是相同的,是同一个数。
1 === 1.0 // true
JavaScript 语言的底层根本没有整数,所有数字都是小数(64位浮点数)。容易造成混淆的是,某些运算只有整数才能完成,此时 JavaScript 会自动把64位浮点数,转成32位整数,然后再进行运算,
由于浮点数不是精确的值,所以涉及小数的比较和运算要特别小心。比如经典的
0.1 + 0.2 === 0.3 // false
# 2. 数值精度
我们以最简单的32位单精度浮点型数的二进制存储举例子, 先看图
- S是符号位
- M的有效数字,
1 <= M <2
- E表示指数位
举个例子5.0, 写成二进制的表示为101.0, 写成公式就是 5.0 = (-1)^0 * 1.01 * 2^2
这里不要以十进制的思维去思考, 得出什么4.04 这样子奇怪的数字, 在二进制中,乘以2就是小数点右移一位,除以2也就是乘以2^-1,小数点左移一位。所以 1.01 * 2^2 === 101
WARNING
0.15625 转成二进制是0.00101 所以M是1.01, E存的值为124,实际为124 - 127 = -3
, 所以0.00101 = 1.01 * 2^-3
十进制小数怎么转换为二进制
0.15625 * 2 = 0.3125 // 取0
0.3125 * 2 = 0.625 //再取0
0.625 * 2 = 1.25 此时取1
0.25 * 2 = 0.5 取0
0.5 * 2 = 1 取1 此时结束
从上往下读取取出的数00101 最后补上小数点即为0.00101
那么如果是10.15625呢? 就是1010.00101
js如何快速的进行进制转换
const x = 0.15625
x.toString(2) // '0.00101'
const y = 10.15625
y.toString(2) // '1010.00101'
const z = 0.3
z.toString(2) // '0.010011001100110011001100110011001100110011001100110011'
IEEE 754 对有效数字M和指数E,还有一些特别规定。
DANGER
十进制小数入内存是极有可能失精度的比如 0.3 就无法转化为有限位数二进制表示。
有效数字 M
前面说过,1≤ M <2,也就是说,M 可以写成 1.xxxxxx 的形式,其中 xxxxxx 表示小数部分。IEEE754 规定,在计算机内部保存 M 时,默认这个数的第一位总是 1,因此可以被舍去,只保存后面的 xxxxxx 部分。比如保存 1.01 的时候,只保存 01,等到读取的时候,再把第一位的1加上去。这样做的目的,是节省 1 位有效数字。以32位浮点数为例,留给 M 只有 23 位,将第一位的1舍去以后,等于可以保存 24 位有效数字。
指数 E
首先,E 为一个无符号整数(unsignedint)。这意味着,如果E为8位,它的取值范围为 0~255;如果 E 为 11 位,它的取值范围为 0~2047。但是,我们知道,科学计数法中的E是可以出现负数的,所以 IEEE754 规定,E 的真实值必须再减去一个中间数,对于 8 位的 E,这个中间数是 127;对于 11 位的E,这个中间数是 1023。
比如,2^10 的 E 是 10,所以保存成 32 位浮点数时,必须保存成 10+127=137,即 10001001。
然后,指数E还可以再分成三种情况:
- (1)E不全为0或不全为1。这时,浮点数就采用上面的规则表示,即指数E的计算值减去127(或1023),得到真实值,再将有效数字M前加上第一位的1。
- (2)E 全为 0。这时,浮点数的指数 E 等于 1-127(或者 1-1023),有效数字 M 不再加上第一位的 1,而是还原为 0.xxxxxx 的小数。这样做是为了表示 ±0,以及接近于 0 的很小的数字。
- (3)E 全为 1。这时,如果有效数字 M 全为 0,表示 ± 无穷大(正负取决于符号位 s);如果有效数字 M 不全为 0,表示这个数不是一个数(NaN)。
# js的数值精度
根据 IEEE 754 标准, js浮点数的64位二进制是这样子的组成的, 从左到右
WARNING
第1位:符号位, 0表示负数,1表示证书
第2到12位(共11位):指数部分
第13到64位(共52位):有效数字
指数部分一共有11个二进制位,因此大小范围就是0到2047。IEEE 754 规定,如果指数部分的值在0到2047之间(不含两个端点),那么有效数字的第一位默认总是1,不保存在64位浮点数之中。也就是说,有效数字这时总是1.xx...xx
的形式,其中xx..xx
的部分保存在64位浮点数之中,最长可能为52位。因此,JavaScript 提供的有效数字最长为53个二进制位
V = (-1)^S * 1.xx...xx * 2^指数部分
上面公式是正常情况下(指数部分在0到2047之间, 如果不在这个区间是另外的办法,比如全是0或者全是1),一个数在 JavaScript 内部实际的表示形式。
精度最多只能到53个二进制位,这意味着,绝对值小于等于2的53次方的整数,即-2^53到2^53,都可以精确表示。
console.log(Math.pow(2, 53)); // 9007199254740992
console.log(Math.pow(2, 53) + 1); // 9007199254740992 真实应该是9007199254740993
console.log(Math.pow(2, 53) + 2); // 9007199254740994
console.log(Math.pow(2, 53) + 3); // 9007199254740996
比如上面的代码, 超出精度之后就显示的不对了。所以,大于2的53次方的数值,都无法保持精度。由于2的53次方是一个16位的十进制数值,所以简单的法则就是,JavaScript 对15位的十进制数都可以精确处理。
个人理解
这是由有效数字决定的,超出2^53次方, 意味着53位的二进制存不下有效数字了, 所以精度丢失。
# 3. 数值范围
根据标准,64位浮点数的指数部分的长度是11个二进制位,意味着指数部分的最大值是2047(2的11次方减1)。也就是说,64位浮点数的指数部分的值最大为2047,分出一半表示负数,则 JavaScript 能够表示的数的指数范围为2^1024到2^-1023(开区间),超出这个范围的数无法表示。
如果一个数大于等于2的1024次方,那么就会发生“正向溢出”,即 JavaScript 无法表示这么大的数,这时就会返回Infinity
。
Math.pow(2, 1025) // Infinity
如果一个数小于等于2的-1075次方(指数部分最小值-1023,再加上小数部分的52位),那么就会发生为“负向溢出”,即 JavaScript 无法表示这么小的数,这时会直接返回0。
Math.pow(2, -1075) // 0
console.log(Math.pow(10, 300)); // 1e+300
console.log(Math.pow(2, 1023)); // 8.98846567431158e+307
个人理解
比如上面的示例,1e+300 表示10的300次方, 有效数字是我们可以看做1 这里不存在精度丢失。 8.98846567431158e+307的有效数字可以理解为8.98846567431158这里就必然存在精度丢失了。因为这个数字的数字表示可以看下面的代码,这么长一串, 所以整数部分精度肯定是丢失的。 但是js依然可以使用指数形式表示这么一个数字, 虽然不是很精确, 就是我们看到的 8.98846567431158e+307
89884656743115795386465259539451236680898848947115328636715040578866337902750481566354238661203768010560056939935696678829394884407208311246423715319737062188883946712432742638151109800623047059726541476042502884419075341171231440736956555270413618581675255342293149119973622969239858152417678164812112068608
另外的例子
let x = 0.5;
for (let i = 1; i <= 25; i++) {
console.log(x);
x = x * x;
}
console.log(x); // 0
连续对结果做平方计算,导致最后的结果超出可表示的范围,就变成了0。
JavaScript 提供Number
对象的MAX_VALUE
和MIN_VALUE
属性,返回可以表示的具体的最大值和最小值。
Number.MAX_VALUE // 1.7976931348623157e+308
Number.MIN_VALUE // 5e-324
# 4. 数值的表示法
JavaScript 的数值有多种表示方法,可以用字面形式直接表示,比如35
(十进制)和0xFF
(十六进制)。
数值也可以采用科学计数法表示,下面是几个科学计数法的例子。
let x = 123e3;
console.log(x); // 123,000
console.log(123e-3); // 0.123
console.log(-3.1e+12); // -3,100,000,000,000
科学计数法允许字母e
或E
的后面,跟着一个整数,表示这个数值的指数部分。
以下两种情况,JavaScript 会自动将数值转为科学计数法表示,其他情况都采用字面形式直接表示。
(1)小数点前的数字多于21位。
console.log(1234567890123456789012); // 1.2345678901234568e+21
console.log(123456789012345678901); // 123456789012345680000 可以看到这里就存在有效位数精度丢失
(2)小数点后的零多于5个。
console.log(0.0000011); // 0.0000011
console.log(0.000000011); // 1.1e-8
# 5. 数值的进制
使用字面量(literal)直接表示一个数值时,JavaScript 对整数提供四种进制的表示方法:十进制、十六进制、八进制、二进制。
进制
- 十进制:没有前导0的数值。
- 八进制:有前缀
0o
或0O
的数值,或者有前导0、且只用到0-7的八个阿拉伯数字的数值。 - 十六进制:有前缀
0x
或0X
的数值。 - 二进制:有前缀
0b
或0B
的数值。
默认情况下,JavaScript 内部会自动将八进制、十六进制、二进制转为十进制
console.log(0xff); // 255
console.log(0o377); // 255
console.log(0b11); // 3
// 如果八进制、十六进制、二进制的数值里面,出现不属于该进制的数字,就会报错。
console.log(0xgg); // SyntaxError: Invalid or unexpected token
常来说,有前导0的数值会被视为八进制,但是如果前导0后面有数字8
和9
,则该数值被视为十进制。
console.log(077); // 63 => 7 * 8 + 7 = 63
console.log(088); // 88
前导0表示八进制,处理时很容易造成混乱。ES5 的严格模式和 ES6,已经废除了这种表示法,但是浏览器为了兼容以前的代码,目前还继续支持这种表示法。
# 6. 特殊值
# 6.1 +0 -0
正副0的区别其实就是符号位不同。其他是等价的, 比如
+0 === -0 && 0 === +0 && 0 === -0 // true
console.log(+0); // 0
console.log(-0); // 0
console.log((+0).toString()); // '0'
console.log((-0).toString()); // '0'
不过也有点区别, 比如把0当做分母的时候
console.log(1 / +0); // Infinity
console.log(1 / -0); // -Infinity
# 6.2 NaN
NaN
是 JavaScript 的特殊值,表示“非数字”(Not a Number),主要出现在将字符串解析成数字出错的场合。
console.log(parseInt('qwe')); // NaN
console.log(5 - 'x'); // NaN
console.log(0 / 0); // NaN
WARNING
需要主义的是NaN不是一个类型, 而是一个特殊的数值, 他的数值类型依旧是Number
typeof NaN === number // true
还有就是NaN不等于他自己
console.log(NaN === NaN); // false
如果需要判断一个值是不是NaN, 可以采用全局的 isNaN
或者Number.isNaN
, 建议采用后者。
console.log(isNaN(NaN)); // true
console.log(isNaN('')); // false
console.log(isNaN(true)); // false
console.log(isNaN(undefined)); // true
如果isNaN
函数的参数不是Number
类型, isNaN
函数会首先尝试将这个参数转换为数值,然后才会对转换后的结果是否是NaN进行判断。因此,对于能被强制转换为有效的非NaN数值来说(空字符串和布尔值分别会被强制转换为数值0和1),返回false
值也许会让人感觉莫名其妙。比如说,空字符串就明显“不是数值(not a number)”
Number.isNaN()
方法确定传递的值是否为NaN,并且检查其类型是否为 Number。它是原来的全局isNaN的更稳妥的版本。
Number.isNaN(NaN); // true
Number.isNaN(Number.NaN); // true
Number.isNaN(0 / 0) // true
Number.isNaN("NaN"); // false,字符串 "NaN" 不会被隐式转换成数字 NaN。
Number.isNaN(undefined); // false
Number.isNaN({}); // false
Number.isNaN("blabla"); // false
# 6.3 Infinity
Infinity
表示“无穷”,用来表示两种场景。一种是一个正的数值太大,或一个负的数值太小,无法表示;另一种是非0数值除以0,得到Infinity
。
// 场景一
Math.pow(2, 1024)
// Infinity
// 场景二
0 / 0 // NaN
1 / 0 // Infinity
Infinity
有正负之分,Infinity
表示正的无穷,-Infinity
表示负的无穷。
Infinity === -Infinity // false
1 / -0 // -Infinity
-1 / -0 // Infinity
由于数值正向溢出(overflow)、负向溢出(underflow)和被0
除,JavaScript 都不报错,而是返回Infinity
,所以单纯的数学运算几乎没有可能抛出错误。
Infinity
大于一切数值(除了NaN
),-Infinity
小于一切数值(除了NaN
)。
Infinity > 1000 // true
-Infinity < -1000 // true
Infinity
的四则运算,符合无穷的数学计算规则。
5 * Infinity // Infinity
5 - Infinity // -Infinity
Infinity / 5 // Infinity
5 / Infinity // 0
0 * Infinity // NaN
0 / Infinity // 0
Infinity / 0 // Infinity
Infinity + Infinity // Infinity
Infinity * Infinity // Infinity
Infinity - Infinity // NaN
Infinity / Infinity // NaN
// Infinity与null计算时,null会转成0,等同于与0的计算。
null * Infinity // NaN
null / Infinity // 0
Infinity / null // Infinity
// Infinity与undefined计算,返回的都是NaN。
undefined + Infinity // NaN
undefined - Infinity // NaN
undefined * Infinity // NaN
undefined / Infinity // NaN
Infinity / undefined // NaN
# 7. 与数值相关的全局方法
# 7.1 parseInt
# 基本使用
parseInt
方法用于将字符串转为整数。
parseInt('123') // 123
// 如果字符串头部有空格,空格会被自动去除。
parseInt(' 81') // 81
// 如果parseInt的参数不是字符串,则会先转为字符串再转换。
parseInt(1.23) // 1
// 等同于
parseInt('1.23') // 1
字符串转为整数的时候,是一个个字符依次转换,如果遇到不能转为数字的字符,就不再进行下去,返回已经转好的部分。
parseInt('8a') // 8
parseInt('12**') // 12
parseInt('12.34') // 12
parseInt('15e2') // 15
parseInt('15px') // 15
如果字符串的第一个字符不能转化为数字(后面跟着数字的正负号除外),返回NaN
。
parseInt('abc') // NaN
parseInt('.3') // NaN
parseInt('') // NaN
parseInt('+') // NaN
parseInt('+1') // 1
所以,parseInt
的返回值只有两种可能,要么是一个十进制整数,要么是NaN
。
如果字符串以0x
或0X
开头,parseInt
会将其按照十六进制数解析。
parseInt('0x10') // 16
如果字符串以0
开头,将其按照10进制解析。
parseInt('011') // 11
对于那些会自动转为科学计数法的数字,parseInt
会将科学计数法的表示方法视为字符串,因此导致一些奇怪的结果。
parseInt(1000000000000000000000.5) // 1
// 等同于
parseInt('1e+21') // 1
parseInt(0.0000008) // 8
// 等同于
parseInt('8e-7') // 8
# 进制转换
parseInt
方法还可以接受第二个参数(2到36之间),表示被解析的值的进制,返回该值对应的十进制数。默认情况下,parseInt
的第二个参数为10,即默认是十进制转十进制。
parseInt('1000', 2) // 8
parseInt('1000', 6) // 216
parseInt('1000', 8) // 512
上面代码中,二进制、六进制、八进制的1000
,分别等于十进制的8、216和512。这意味着,可以用parseInt
方法进行进制的转换。
如果第二个参数不是数值,会被自动转为一个整数。这个整数只有在2到36之间,才能得到有意义的结果,超出这个范围,则返回NaN
。如果第二个参数是0
、undefined
和null
,则直接忽略。
parseInt('10', 37) // NaN
parseInt('10', 1) // NaN
parseInt('10', 0) // 10
parseInt('10', null) // 10
parseInt('10', undefined) // 10
如果字符串包含对于指定进制无意义的字符,则从最高位开始,只返回可以转换的数值。如果最高位无法转换,则直接返回NaN
。
parseInt('1546', 2) // 1
parseInt('546', 2) // NaN
前面说过,如果parseInt
的第一个参数不是字符串,会被先转为字符串。这会导致一些令人意外的结果。
parseInt(0x11, 36) // 43
parseInt(0x11, 2) // 1
// 等同于
parseInt(String(0x11), 36)
parseInt(String(0x11), 2)
// 等同于
parseInt('17', 36)
parseInt('17', 2)
十六进制的0x11
会被转换为十进制的17 ,然后再转成字符串.
这种处理方式,对于八进制的前缀0,尤其需要注意。
parseInt(011, 2) // NaN
// 等同于
parseInt(String(011), 2)
// 等同于
parseInt(String(9), 2)
上面代码中,第一行的011
会被先转为字符串9
,因为9
不是二进制的有效字符,所以返回NaN
。如果直接计算parseInt('011', 2)
,011
则是会被当作二进制处理,返回3。
# 7.2 parseFloat
parseFloat
方法用于将一个字符串转为浮点数。
如果字符串符合科学计数法,则会进行相应的转换。
parseFloat('314e-2') // 3.14
parseFloat('0.0314E+2') // 3.14
如果字符串包含不能转为浮点数的字符,则不再进行往后转换,返回已经转好的部分。
parseFloat('3.14more non-digit characters') // 3.14
parseFloat
方法会自动过滤字符串前导的空格。
parseFloat('\t\v\r12.34\n ') // 12.34
如果参数不是字符串,或者字符串的第一个字符不能转化为浮点数,则返回NaN
。
parseFloat([]) // NaN
parseFloat('FF2') // NaN
parseFloat('') // NaN
上面代码中,尤其值得注意,parseFloat
会将空字符串转为NaN
。
这些特点使得parseFloat
的转换结果不同于Number
函数。
parseFloat(true) // NaN
Number(true) // 1
parseFloat(null) // NaN
Number(null) // 0
parseFloat('') // NaN
Number('') // 0
parseFloat('123.45#') // 123.45
Number('123.45#') // NaN
# 7.3 isNaN()、 Number.isNaN()
这个上面已经说过了
# 7.4 isFinite()
isFinite
方法返回一个布尔值,表示某个值是否为正常的数值。
isFinite(Infinity) // false
isFinite(-Infinity) // false
isFinite(NaN) // false
isFinite(undefined) // false
isFinite(null) // true
isFinite(-1) // true
除了Infinity
、-Infinity
、NaN
和undefined
这几个值会返回false
,isFinite
对于其他的数值都会返回true
。
学习资源:ruanyifeng (opens new window)、菜鸟 (opens new window)。针对大佬的补充以及这几年的前端变化做了一些内容新增