IEEE 754 64位浮点数:
在 JavaScript 中,采用 IEEE 754 双精度浮点数来存储数值,表示格式如下:
- 第1位(Sign):符号位,
0
表示正数,1
表示负数 - 第2位到第12位(Exponent)(共11位):指数部分
- 第13位到第64位(Mantissa)(共52位):小数部分(即有效数字)
我们能够发现,越高的位置对数值的影响度就越大,S > E > M,只要改变符号位 0-1,数值的大小就会发生天差地别的变化。符号位决定了一个数的正负,指数部分决定了数值的大小,小数部分决定了数值的精度。
M部分隐含的一位
注意:小数部分是用科学计数法表示的,所以,默认是以 1.xxx...xxx 的,在小数位保存的只是小数部分,而不包括 1,它是不保存在64位浮点数之中。所有,实际的精度是 53 位的,这也就是为什么 JavaScript 中 有效精度位 2**53 。
指数部分表示
根据标准,64位浮点数的指数部分的长度是11个二进制位,意味着指数部分的最大值是2047(2的11次方减1)。也就是说,64位浮点数的指数部分的值最大为2047,分出一半表示负数,则 JavaScript 能够表示的数值范围为21024到2-1023(开区间),超出这个范围的数无法表示。
0.1的表示
0.1 的二进制为:
0.0 0011 0011 0011 无限循环0011
采用科学计数法,表示 0.1 的二进制: (下列为公式,一个数在 JavaScript 内部实际的表示形式。)
(-1)^符号位 * 1.xx...xx * 2^指数部分
0.00011001100110011001100110011001100110011001100110011 无限循环0011
2^(-4) * (1.1001100110011循环0011)
(-1)^0 * 2^(-4) * (1.1 0011 0011 0011 循环0011) // 二进制
指数还是按照十进制来换算的,而尾数是二进制的表示形式。
(1 * 2^0 + 1 * 2^-1 + 0 * 2^-1 .... ) * 0.. 625 // 十进制
0.1 双浮点数存储结构:
三个部分:
S = 0 满足条件 //十进制 E = 1019 不满足条件,需要转为 11 位的二进制 //二进制 M = 1001100110011循环0011 不满足条件,需要转为 52 位的二进制
将 E 转为 11 位二进制
1111111011 ,共 10 位,但 E 要 11 位,所以要在首部补 0 E = 01111111011
将 M 填充完整
1 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011
要求 M 为 52 位,所以需要 0 舍 1 入。
M = 1001100110011001100110011001100110011001100110011010 //共 52 位
得到
S = 0 E = 01111111011 M = 1001100110011001100110011001100110011001100110011010
拼接 SEM 得到 64 位双精度浮点数:
0011111110111001100110011001100110011001100110011001100110011010
可以自己验证,可以转回科学计数法表示的二进制,在转为十进制。
V = (-1)^0 * 2^(-4) * (1.1001100110011001100110011001100110011001100110011010) = 1.60000000000000008881784197001E0 * 0.625 = 0.100000000000000005551115123126
也可以借助工具
因为 mantissa 固定长度是 52 位,再加上省略的一位,最多可以表示的数是,2^53=9007199254740992,对应科学计数尾数是
9.007199254740992 * 2^15,
它的长度是 16,所以可以使用toPrecision(16)
来做精度运算,超过的精度会自动做凑整处理0.10000000000000000555.toPrecision(16) // 返回 0.1000000000000000,去掉末尾的零后正好为 0.1 // 但你看到的 `0.1` 实际上并不是 `0.1`。不信你可用更高的精度试试: 0.1.toPrecision(21) = 0.100000000000000005551
从上面我们可以看出,0.1 都不是准确的 0.1 ,只要是小数(不包括最后一位为5)的小数,在保留52位的时候,都是无限的,所以都会造成精度丢失。
0.2的表示
0.2 的二进制为:0.0011 0011 0011(0011循环)
公式 表示
(-1)^0 * 2^(-3) * (1. 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1010)
Double 表示
S = 0 E = 1020,二进制为 01111111100 M = 1001100110011001100110011001100110011001100110011010 SEM = 0011111111001001100110011001100110011001100110011001100110011010
保留16位精度,得到 0.2 。
0.1 + 0.2第一种方法:
我们用 0.1 和 0.2 的二进制表示相加,在进行转 Double双精度。
0.1 = 0.00011001100110011001100110011001100110011001100110011010
0.2 = 0.0011001100110011001100110011001100110011001100110011010
0.00011001100110011001100110011001100110011001100110011010 0.0011001100110011001100110011001100110011001100110011010 + --------------------------------------------------------------------------- 0.01001100110011001100110011001100110011001100110011001110
结果为:0.01 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 10
转化为 Double,即 SEM:
(-1)^0 * 2^(-2) * (1.0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0100 ) S = 0 E = 1021,二进制为 01111111101 最后的 10 被舍掉,并且进位 M = 0011001100110011001100110011001100110011001100110100 SEM = 0011111111010011001100110011001100110011001100110011001100110100
0.1 + 0.2 先求两个的Double双精度表示形式,在进行相加,最后以十进制表示
0.1 表示的 SEM:
0 01111111011 1001100110011001100110011001100110011001100110011010
0.2 表示的 SEM:
0 01111111100 1001100110011001100110011001100110011001100110011010
接下来,计算 0.1 + 0.2 。
注意:浮点数进行计算时,需要对阶。即把两个数的 E 设置为一样的值,然后再计算 M 部分。其实对阶很好理解,就和我们十进制科学记数法加法一个道理,先把 E 部分化成一样,再计算 M。
另外,需要注意一下,对阶时需要小阶对大阶。因为,这样相当于,小阶指数乘以倍数,尾数部分相对应的除以倍数,在二进制中即右移倍数位。这样,不会影响到 M 的高位,只会移出低位,损失相对较少的精度。
因此,0.1的阶码为 -4 , 需要对阶为 0.2的阶码 -3 。M 部分整体右移一位。
原来的0.1 0 01111111011 1001100110011001100110011001100110011001100110011010 对阶后的0.1 0 01111111100 1100110011001100110011001100110011001100110011001101
然后进行 M 部分相加
0 01111111100 1100110011001100110011001100110011001100110011001101 + 0 01111111100 1001100110011001100110011001100110011001100110011010 = 0 01111111100 10110011001100110011001100110011001100110011001100111
可以看到,产生了进位。因此,E 需要 +1,即为 -2,M 部分进行低位四舍五入处理。因M 最低位为1,需要进位,所以存储为:
0 01111111101 0011001100110011001100110011001100110011001100110100
工具计算:
总结精度丢失问题:
第一种:
1. 在十进制转换为二进制的过程中,会产生精度的损失。
2. 在二进制相加过程中,也产生了精度的丢失。
第二种:
1. 在十进制转换为二进制的过程中,会产生精度的损失。
2. 二进制浮点数进行对阶运算时,也会产生精度的损失。
因此,最终结果才产生了偏差。