之前写过一篇“谈谈JavaScript的算数运算、浮点数二进制表示舍入误差及比较、类型转换和变量声明提前问题”,当时主要是阐述浮点数运算产生的舍入误差及js类型转换和变量申明提前问题,所以只是略微提及了js中浮点数二进制表示问题。现在回过头来看,如果不掌握IEEE754标准的浮点数二进制表示,是不会彻底理解js浮点数运算的舍入误差及类似0.1+0.2!=0.3浮点数比较问题。因此本文将要讨论的“Javascript IEEE754标准浮点数二进制表示、浮点数运算及Js为啥0.1+0.2!=-0.3”更多地是对之前博文的补充。
1、二进制小数如何表示
我们先来了解下浮点数10.15,用二进制如何表示?
整数部分,大家应该很熟悉,(10) = (1010)
小数部分,我们按乘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 + 0 *2 + 1 * 2 + … = 0.125 + …
其实大家已经看出来了,由于二进制存储位数限制,不可能一直循环下去,因此0.15用二进制表示时,必然产生舍入精度失真问题。
至此,我们可以得到看上去有点像的二进制小数啦:
- (10.15) = (1010.00100110011001…1001…1001)
- (10.5) = (1010.1)
再仔细看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) * M * 2 ,其中:
一般来说,现在的编译器都支持两种浮点格式,一种是单精度,一种是双精度。单双精度分别对应于编程语言当中的float和double类型。其中float是单精度的,采用32位二进制表示,其中1位符号位,8位阶码以及23位尾数。double是双精度的,采用64位二进制表示,其中1位符号位,11位阶码以及52位尾数,js中的浮点数就是双进度的。如下表所示:
我们以javascript为例,其浮点数是64位,因此一个十进制浮点数(10.5)转换成二进制,如下:
1)尾数是有限位(如1/2,1/4,1/8, x/2等),以上文提及的10.5为例:
2)尾数是无限循环,以上文提及的10.15为例:
3)两个转换工具,十进制浮点数转二进制表示,十六进制浮点数转十进制
3、0.1+0.2!=0.3说明
假设:x = 0.1 = 1.1001100… * 2
0 01111111011 1001100110011001100110011001100110011001100110011010
y = 0.2 = 1.100110011… * 2
0 01111111100 1001100110011001100110011001100110011001100110011010
大学教材里,浮点数运算,都是双符号位补码、移码进行运算,本文试着以IEEE754标准进行浮点数运算。浮点数运算都有下述几个步骤:
1)对阶处理
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标准的浮点数二进制表示,浮点数简单运算,包括: