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. 二进制浮点数进行对阶运算时,也会产生精度的损失。

因此,最终结果才产生了偏差。