Javascript IEEE754标准的浮点数二进制表示、浮点数运算及Js为啥0.1+0.2!=-0.3

  之前写过一篇“谈谈JavaScript的算数运算、浮点数二进制表示舍入误差及比较、类型转换和变量声明提前问题”,当时主要是阐述浮点数运算产生的舍入误差及js类型转换和变量申明提前问题,所以只是略微提及了js中浮点数二进制表示问题。现在回过头来看,如果不掌握IEEE754标准的浮点数二进制表示,是不会彻底理解js浮点数运算的舍入误差及类似0.1+0.2!=0.3浮点数比较问题。因此本文将要讨论的“Javascript IEEE754标准浮点数二进制表示、浮点数运算及Js为啥0.1+0.2!=-0.3”更多地是对之前博文的补充。

1、二进制小数如何表示

我们先来了解下浮点数10.15,用二进制如何表示?
整数部分,大家应该很熟悉,(10)10 = (1010)2
小数部分,我们按乘2余1法则,将十进制小数转为二进制小数,具体如下:

0.15 * 2 = 0.3 < 1,		0
0.3 * 2 = 0.6 < 1, 		0
0.6 * 2 = 1.2 > 1,		1
0.2 * 2 = 0.4 < 1,		0
0.4 * 2 = 0.8 < 1,		0
0.8 * 2 = 1.6 > 1,		1
0. 6 * 2 = 1.2 > 1,		1,开始1001循环啦

则0.15转换成二进制:00100110011001…1001…1001 = 0 * 2-1 + 0 *2-2 + 1 * 2-3 + … = 0.125 + …
其实大家已经看出来了,由于二进制存储位数限制,不可能一直循环下去,因此0.15用二进制表示时,必然产生舍入精度失真问题。

至此,我们可以得到看上去有点像的二进制小数啦:

  • (10.15)10 = (1010.00100110011001…1001…1001)2
  • (10.5)10 = (1010.1)2

再仔细看1010.00100110011001…1001…1001,1010.1,还有小数点呢,让只认识0和1的cpu、寄存器情何以堪?为此,我们需要进一步处理,将符号、小数点也数字化,基本思路就是:

  • 0 表示正数,1表示负数,将符号1,0数字化;
  • 用科学计数将小数整数化;

本文以IEEE754为例,我们看下十进制浮点数如何表示成二进制浮点数进行存储。

2、IEEE754标准解读

IEEE,电气和电子工程师协会( 全称是Institute of Electrical and Electronics Engineers)是一个国际性的电子技术与信息科学工程师的协会,是目前全球最大的非营利性专业技术学会,IEEE 754 标准是IEEE二进位浮点数算术标准(IEEE Standard for Floating-Point Arithmetic)的标准编号。
IEEE 浮点标准表示: V = (-1)s * M * 2E ,其中:

  • s 是符号位,0表示正,1表示负;
  • M为尾数,是一个二进制小数,它的范围是0至1-ε,或者1至2-ε(ε的值一般是2-k次方,其中设k > 0)
  • E为阶码,可正可负,作用是给尾数加权。

  一般来说,现在的编译器都支持两种浮点格式,一种是单精度,一种是双精度。单双精度分别对应于编程语言当中的float和double类型。其中float是单精度的,采用32位二进制表示,其中1位符号位,8位阶码以及23位尾数。double是双精度的,采用64位二进制表示,其中1位符号位,11位阶码以及52位尾数,js中的浮点数就是双进度的。如下表所示:

数据类型 符号位 阶码 尾数 总位数 偏移值
单精度 1 8 23 32 127
双精度 1 11 52 64 1023

我们以javascript为例,其浮点数是64位,因此一个十进制浮点数(10.5)转换成二进制,如下:
1)尾数是有限位(如1/2,1/4,1/8, x/2n等),以上文提及的10.5为例:

  • 确定符号位,正数,s = 0;
  • 确定阶数
    10.5 = 1010.1 = 1.0101 * 2 3,因此阶数 = 3,阶码 = 3 + 1023 = 1026 = 10000000010
  • 尾数,0101,不足52位的,右侧补0,因此10.5 用二进制表示,如下:
    0 10000000010 0101000000000000000000000000000000000000000000000000
  • 小数点前面的1去哪里了?由于规格化尾数最高位总是“1”,所以直接隐藏掉,这样可以多出1位存储位置,提高精度

2)尾数是无限循环,以上文提及的10.15为例:

  • 确定符号位,正数,s = 0;
  • 确定阶数
    10.15 = 1010.00100110011001…1001…1001 = 1.0100010011001… * 2 3,因此阶数 = 3,阶码 = 3 + 1023 = 1026 = 10000000010
  • 尾数,超出52位,第53位进行舍入并丢弃53位后面的位数,因此
    10.15 = 0 10000000010 0100010011001100110011001100110011001100110011001100 1
    尾数部分是01000+12个1001循环,其实还可以循环下去,由于存储位数限制,只能是52位,黄色标注部分已经是5 + 12*4 = 53位啦,因此最后1位1要进行舍入操作,1入0舍,因此最终:
    10.15 = 0 10000000010 0100010011001100110011001100110011001100110011001101

3)两个转换工具,十进制浮点数转二进制表示,十六进制浮点数转十进制

扫描二维码关注公众号,回复: 9213751 查看本文章

3、0.1+0.2!=0.3说明

假设:x = 0.1 = 1.1001100… * 2 -4
0 01111111011 1001100110011001100110011001100110011001100110011010
y = 0.2 = 1.100110011… * 2 -3
0 01111111100 1001100110011001100110011001100110011001100110011010


大学教材里,浮点数运算,都是双符号位补码、移码进行运算,本文试着以IEEE754标准进行浮点数运算。浮点数运算都有下述几个步骤:

  • 对阶,小阶向大阶对齐;
  • 尾数运算;
  • 规格化处理;
  • 舍入处理;
  • 溢出判断;
1)对阶处理
  • x 阶码 = 01111111011
  • y 阶码 = 01111111100
  • x-y 阶码 = 01111111011 - 01111111100 = 01111111011 + (-01111111100)
    = 01111111011 + 10000000100 = 111111111111 = -1
    即 x阶码需要+1,尾数需要右移一位,采用舍入法则,末位0舍去,如下:
    x = 0 01111111100 1100110011001100110011001100110011001100110011001101
    y = 0 01111111100 1001100110011001100110011001100110011001100110011010
2)尾数向加

对尾数(52位)+ 1位隐藏位(通常是1)进行运算,如果尾数发生右移(随带隐藏位发生右移),则默认补0。

x: 0.1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1101
+y 1.1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1010
---------------------------------------------------------------------
  10.0110 0110 0110 0110 0110 0110 0110 0110 0110 0110 0110 0110 0111
3)结果规格化

简单理解就是尾数是否1.xxxxxx格式,目前是10.xxx,因此需要做规格化处理,即尾数需要右移1位(也称右规),同时阶码+1,如下:

1.00110 0110 0110 0110 0110 0110 0110 0110 0110 0110 0110 0110 011(1)
0 01111111101 0011001100110011001100110011001100110011001100110011(1)

规格化后,尾数高位1,隐藏不显示。

4)舍入处理

规格化时,尾数末位如果是1,直接移除将会丢失进度,因此需要舍入处理,通常采用“1入0舍”法,本例尾数末位是1,因此需要“入”,即+1处理

  0 01111111101 0011001100110011001100110011001100110011001100110011
+ 0 01111111101 0000000000000000000000000000000000000000000000000001
= 0 01111111101 0011001100110011001100110011001100110011001100110100
5)溢出判断

结果:0 01111111101 0011001100110011001100110011001100110011001100110100,没右发生溢出,故0.1+0.2结果为:
0 01111111101 0011001100110011001100110011001100110011001100110100
转16进制:3fd3333333333334,用工具IEEE-754.old/64bit.html,得到十进制浮点数结果为:
0.1+0.2 = 0.30000000000000004

4、总结

至此,我们已经了解IEEE754标准的浮点数二进制表示,浮点数简单运算,包括:

  • 十进制浮点数如何按IEEE754标准表示为二进制浮点数;
  • 二进制浮点数加法运算;
  • 解释了javascript中为啥:0.1+0.2=0.30000000000000004 != 0.3;
  • 除此之外,还推荐了两个IEEE的数进制转换工具;
发布了294 篇原创文章 · 获赞 98 · 访问量 77万+

猜你喜欢

转载自blog.csdn.net/chuangxin/article/details/96768876