JavaScript中所有的数字,无论是整数还是小数,其类型都是 Number,遵循
IEEE 754
程序内部用一个 64 位固定长度的二进制进行存储表示。JavaScript 中的浮点数进行运算时,经常会遇到计算精度问题,例如经典的
0.1+0.2=0.30000000000000004
,本文将探究 JavaScript 的浮点数,并解释为何
0.1+0.2=0.30000000000000004
IEEE 754
标准的 64 位 double 双精度浮点数(与之相关的还有 32 位 float 单精度浮点数)。该规范定义了浮点数的格式。
对于 32 位的浮点数,最高的 1 位是符号位 S,接着的 8 位是指数 E,剩下的 23 位为尾数位 M
对于 64 位的浮点数,最高的 1 位是符号位 S,接着的 11 位是指数 E,剩下的 52 位为尾数位 M
符号位 S:0 表示正数,1 表示负数
指数 E:表示次方
尾数位 M:表示有效数字(小数部分)
实际数字的计算公式为:
接下来只对 64 位浮点数的二进制表示进行分析:
该计算公式遵循科学计数法的规范,对于十进制表示而言,尾数的范围是
0<M<10
;对于二进制表示而言,尾数的范围是
0<M<2
。浮点数二进制表示,所以此处尾数的范围是:
0<M<2
,也就是说 M 的整数位始终是 1,所以可以舍去,只保留后面的小数部分,这样就能表示 53 位了。
指数位 E 是一个无符号整数,64 位浮点数中,指数位长度是 11 位,取值范围是
[0~2047]
,由于科学计数法中指数可正可负,所以,中间数 1023,
[0,1022]
表示为负,
[1024,2047]
表示为正
最终的公式变成:
1
|
V = (-1)^S * (M + 1) * 2^(E - 1023)
|
http://www.binaryconvert.com/convert_double.html
下面以 0.1 为例,对其进行 64 位二进制表示
0.1 转换成二进制:0.0001100110011001100(1100循环),即
1.100110011001100x2^-4
,得到:
指数位 E = -4 + 1023 = 1019 (
1019
11 位二进制表示为:01111111011)
尾数位 M = 100110011001100… (舍去首位 1)
所以十进制 0.1 转换成 64 位浮点数二进制表示为:
1
|
0011111110111001100110011001100110011001100110011001100110011010
|
而将 64 位浮点数二进制的 0.1 转换回十进制时,得到:
1.00000000000000005551115123126E-1
。
但是:此时输出
x
为啥能得到 0.1 呢?
1 2
|
var x = 0.1 console.log(x)
|
分析:
尾数位固定长度 52 位,加上省略的整数位 1,就再加上一位,那么尾数最多能表示的数为:2^53=9007199254740992,对应的十进制科学计数尾数是 9.007199254740992,这也是 JavaScript 最多能表示的精度,长度是 16,所以用
toPrecision(16)
来做精度运算,于是:
1
|
0.10000000000000000555.toPrecision(16)
|
如果使用更高精度,那么可能得到的就不是 0.1
1 2
|
var x = 0.1 console.log(x.toPrecision(21))
|
注意:
toPrecision
方法最大指定精度为 21。对于 0.1 而言,64 位二进制表示,最多能表示的精度为 16 位;转换成十进制后
0.100000000000000005551
,默认的 JavaScript 使用 16 位进行截取,我们最多能使用 21 位进行截取,注意二者的区别。