最近在学习JS过程中发现了一个非常有意思事,就是运算0.1+0.2的结果不是0.3,而是0.30000000000000004,但先将小数做乘法然后相加,再除回来就得到想要的0.3

 

 我用python试了一下,发现python也是一样的,结果也是0.30000000000000004。

 然后我开始信息搜集,最后找到了答案。想知道这其中的原因,要先理解这些点:二进制、指数形式、IEEE 754标准。

 1、二进制

在计算机中所有的数据都是二进制形式存储的,包括整数、浮点数以及其他所有类型的数据。我们将十进制的0.1以及0.2转换成二进制

转换的方法也比较简单,整数转换成二进制:就是用整数除以2然后从下往上取余数,下图用100举例

 

 然后小数转换成二进制就是,小数部分无限乘以2,然后顺序取整。下图用0.375举例

 

 

 

 2、指数形式

用指数方式表示可以在有限的空间里存储更大的数值。

 

所有的十进制数都可以用指数形式表示,成为D=M*10E,比如100可以表示为1*102

二进制数也可以用指数形式表示,B=M*2E的形式,这里的E为指数,M为B的位数。比如0.011可以表示为1.1*10-10,这里的“-10”表示的是-2。

 

3、浮点数IEEE754标准

IEEE 754规定,对于规定了四种表示浮点数值的方式:单精确度(32位)、双精确度(64位)、延伸单精确度(43比特以上,很少使用)与延伸双精确度(79比特以上,通常以80位实现)      //后两个今天不作研究

 

所有的二进制数V都可以用{S,E,M}表示,也就是V=(-1)S×1.M×2E

符号位 S(Sign)能够决定二进制数是正数,(-1)S所以当S为1时结果就是负数,S为0时结果就是正数。(而数值0的符号位会比较特殊)

有效数字位M(Significand)是二进制小数,它的取值范围是1≤1.M<2,大于等于1小于2也就是1.XXXXXXXXX......(单精度是23位,双精度是52位),由于有效数字位的取值整数位永远是1,所以可以被舍去,保留后面的XXXXXXX.....部分,这样就节省了一位有效数字。

指数位 E(Exponent)是 2 的幂(单精度是8位,双精度是52位)是一个无符号整数,就是说它一定是正整数。比如现在E的长度为8位,E的取值范围就是0——255(因为最小就是八个0,最大就是八个1);长度为11位,E的取值范围就是0——2047。但是由于指数位是可以存在负数的,所以为了表达出指数的负数形式设计出了偏移量(单精度137,双精度1023),用E真实的数值加上偏移量计算出指数位(也可以理解为1023表示的是0,小于1023就是负数,大于1023就是正数)。

 

下面以0.375举例

 

 对于指数的值全部为0或者1的情况IEEE 754有特殊规定:

当指数全部为1,有效数字全部为0时,表示的就是正0和负0:

+0:0 00000000000 0000000000......一共64个0

-0:1  00000000000 0000000000......一共63个0

但它其实并不是精确的0,只是最接近0的数值,因为它的值是

V=(-1)^S×1.M×2^E  :(-1)0*1.00000000000......*2-1023,有效数字为这里永远默认有个1

 

当指数全部为1时,如果有效数字M全为0,表示±无穷大;如果有效数字M不全为0,表示这个数不是一个数(NaN)。

V=(-1)^S×1.M×2^E  :(-1)0*1.00000000000......*21023                        //正负取决于S

 

 

 

 

 

 JavaScript中所有数字包括整数和小数都只有一种类型,是遵循 IEEE 754 标准,使用64位固定长度来表示,也就是标准的 double 双精度浮点数。

一、首先将十进制的0.1转换成二进制:0.0011001100110011......

 

 

 

然后将十进制的0.2转换成二进制:0.0011001100110011........

 

 

 

二、将二进制的0.1和0.2转换成指数形式

十进制:0.1

二进制形式:0.000110011001100110011......

指数形式:1.10011001100110011......*2-100          //指数是-4

 

十进制:0.2

二进制形式:0.001100110011001100110011......

指数形式:1.100110011001100110011......*2-11     //指数是-3

 

三、将指数形式转换为IEEE754标准

这里由于IEEE754标准规定了尾数只能保留52位,所以无限循环小数需要做四舍五入,只保留52位小数(就是第53位以后全部舍弃,第53位“0舍1入”,而当第52位为1时,还得再进一位)

 

 

 将十进制0.2转换成IEEE 754标准:

 

 

 

 

 

 

 

四、使用IEEE 754形式的 0.1+0.2    (简单写了)

 

 

简单写的话应该就是10.0100100100100100100100100100100......100111,但是此时得到的数指数依然是指数形式,要将它转换成为二进制形式,就是将小数点向前移三位变成0.01001001001001001001001.....00111

然后将这个数转换为十进制数就是0*2-1+1*2-2+0*2-3+0*2-4.......+1*2-52之后就可以得到0.30000000000000004了

 

总结一下就是:

计算机存储数据时由于空间有限,所以只能存储固定长度的数据,而像一些无限循环的数据只能被四舍五入,所以一些数据可能会和它本身有一些误差,所以做相加运算也会有误差。

 那么最后再看一下(0.1*10+0.2*10)/10为什么等于3,由于0.1和0.2先做乘法运算所以就变成了整数形式,所以有效数字为全部变成了0,避免了计算时的误差,所以就的到了想要的0.3。