JavaScript 为何 0.1 加 0.2 不等于 0.3 ???

带着问题去学习

首先来个计算题

众所周知,但凡有个幼儿园毕业都可以正常的计算以下问题。

1+1=2  

1+2=3    

0.1+0.1 =0.2

0.1+0.2 =0.3

可是,当我在用浏览器 一 一 验证以上结果时,把我给惊呆了。出现下图的情况

聪明的人,一看就知道哪里出问题了。而我是个严谨的人。我们来一个一个验证一下

第一个,一加一等于二,没错没有问题。

第二个,一加二等于三,一样没有问题。

第三个,零点一加零点一等于零点二,还是没有问题。

第四个,零点一加亮点二等于????,什么鬼一大串?为什么不等于零点三?

现在问题产生,为何零点一加零点二不等于零点三?


计算机如何存储数字?

在现实生活中的计算方式

在讨论计算机怎么存储之前,先看看我们平时是怎么算数的?从会说话开始,爸妈就开始教,零一二上是五六七八九十十一十二……,用阿拉伯数字表示如下:0,1,2,3,4,5,6,7,8,9,10,11,12…… ,计算的方式是十进制 ,有十个数字,逢十进一。

什么是逢十进一?我们把个位数进行一步操作,都在前面几个0。形成这样01,02,03,04,05,06,07,08,09,10,11,12……。这样还不是很明显。我们把数字立起来如下所示。

01

02

03

04

05

06

07

08

09

10

11

12

……

从上往下看

个位数的规律是012347890123……,是一个0到9再从0到9一直不断的循环,

十位数的规律是个位数到9 的下一位数字时十位数加1,从原来的0变成了1,在之后个位数到达9的下一位时,十位数再加一变成2,以此类推。这就是十进制,也是我们人类中最常用的一个计算方式。

在计算机世界里的计算方式

计算机的计算方式是二进制,2个数字,逢二进一。

例如:

0000

0001

0010

0011

0100

0101

……

十进制转换成二进制

如何把现实中的十进制转换成计算机的二进制呢?

整数的十进制转二进制

可以通过以下方式例如以十进制的 25 为例

计算过程

先把 25  除以2 等于 12 余数为  1  

然后 12  除以2 等于   6 余数为  0

然后 6    除以 2 等于  3 余数为  0

然后 3    除以 2 等于  1 余数为  1

然后 1    除以 2 等于  0 余数为  1

然后把余数从下往上看为  11001 ,所以十进制25用二进制表示为11001。

那么十进制的小数是怎么转换成二进制的呢?

小数的十进制转二进制。

例如 0.625

计算过程

先把小数 0.625 乘以 2 等于 1.25  个位数为 1

然后小数 0.25   乘以 2 等于 0.5    个位数为 0

然后小数 0.5    乘以 2  等于 1       个位数为 1

从上往下看个位数为 101 ,所以 十进制的0.625 用二进制表示为 0.101。

现在回顾我们刚开始的问题。0.1+0.2 时 为何不等于0.3呢?

我们写的0.1 和 0.2 都是十进制,那我们现在动手吧他们都写成二进制吧

计算十进制0.1 转成二进制

计算过程如下:

0.1 乘以 2 等于 0.2 个位数为 0

0.2 乘以 2 等于 0.4 个位数为 0

0.4 乘以 2 等于 0.8 个位数为 0

0.8 乘以 2 等于 1.6 个位数为 1

0.6 乘以 2 等于 1.2 个位数为 1

0.2 乘以 2 等于 0.4 个位数为 0

0.4 乘以 2 等于 0.8 个位数为 0

0.8 乘以 2 等于 1.6 个位数为 1

0.6 乘以 2 等于 1.2 个位数为 1

……

完蛋,进入了无限循环。

十进制的0.1 用二进制表示就是 0.0001100110011(0011无限循环下去)

再计算十进制的0.2 转成二进制

计算过程如下:

0.2 乘以 2 等于 0.4 个位数为 0

0.4 乘以 2 等于 0.8 个位数为 0

0.8 乘以 2 等于 1.6 个位数为 1

0.6 乘以 2 等于 1.2 个位数为 1

0.2 乘以 2 等于 0.4 个位数为 0

0.4 乘以 2 等于 0.8 个位数为 0

0.8 乘以 2 等于 1.6 个位数为 1

0.6 乘以 2 等于 1.2 个位数为 1

……同样是无限循环下去。

十进制的0.2 用二进制表示就是 0.001100110011(0011无限循环下去)

在十进制转换成二进制时,会出现无限小数,但是计算机的存储空间是有限的,那要怎么办?是如何处理的?那么就引入了计算机数字存储。


JavaScript的数字存储

计算机的存储方式有两种:一种是整数法,另一种是浮点法。

浮点法存放的数字,叫做浮点数(float),浮点法分为单精度和双精度。

在JavaScript中使用的存储方式是双精度的浮点数存储,采用的是 IEEE 754 标准。

存放方式

JS在计算机中。给每个数字开辟一块空间,尺寸固定为64位。

在计算机中,位(bit)是最小的存储单位,简称位bit,

1 byte = 8 bit

1 KB = 1024 byte
1 GB = 1024 MB

对于64位的浮点数,最高的1位是符号位S,接着的11位是指数E,剩下的52位为有效数字M。

下图为64位的浮点数存储示意图

通过上图可知,一个64位的浮点数存储会被分成三部分。

第一段 : 1位,表示位号位,如果为1,是负数,如果为0,是正数

第二段 : 11位,表示指数位,这里的指数是2位底的指数而不是10

第三段 : 52位,表示有效数字

二进制浮点数概念

根据国际标准IEEE754,任意一个二进制浮点数V可以表示成下面的形式:

S*M*2^E

S表示符号位,当s=0,V为正数;当s=1,V为负数。

M表示有效数字,大于等于1,小于2。

2^E表示指数位。

Eg: 5.0 (101.0 )  即1.01*2^2

根据上面标准:S = 0, M = 1.01 , E = 2

IEEE 754对有效数字M和指数E,还有一些特别规定。

前面说过,1≤M<2,也就是说,M可以写成1.xxxxxx的形式,其中xxxxxx表示小数部分。IEEE 754规定,在计算机内部保存 M时,默认这个数的第一位总是1,因此可以被舍去,只保存后面的xxxxxx部分。比如保存1.01的时候,只保存01,等到读取的时候,再把第一位的1加上去。这样做的目的,是节省1位有效数字。以64位浮点数为例,留给M只有52位,将第一位的1舍去以后,等于可以保存53位有效数字。

至于指数E,情况就比较复杂。

首先,E为一个无符号整数(unsigned int)这意味着,如果E为8位,它的取值范围为0~255;如果E为11位,它的取值范围  为0~2047。但是,我们知道,科学计数法中的E是可以出现负数的,所以IEEE 754规定,存入内存时E的真实值必须再加上一个中间数,对于8位的E,
这个中间数是127;对于11位的E,这个中间数是1023。比如,2^10次方的E是10,所以保存成64位浮点数时,必须保存成10+127=137,即10001001。

二进制浮点数实例

以上的概念有些抽象,我们用实际例子出发

例如:存储十进制的25.625

分开整数和小数把十进制转成二进制

通过以上十进制转二进制的例子可知

十进制整数25转成二进制为11001

十进制整数0.625转成二进制为0.101

两个合起来十进制的25.625转成二进制即为11001.101

用科学计数法把二进制转成1.xxxx * 2^n次方的形式

即  1.1001101 = 1.1001101 * 2^4

得到S 为 0 ,E为 1027 二进制为100 0000 0011,M为1001101

存入64位浮点数为按SEM的顺序拼接存储即 0 100 0000 0011 (45个0)100 1101

特殊情况

到这里大致到明白了整个计算机的数字存储,但是还是有一些特殊的情况如下

  • 数字 0 的表示

符号为0 指数为0,尾数为0 表示数字 0

例如:0 00 0000 0000 000000……0000   表示的是0

  • 正无穷

符号为0 指数为2047 ,尾数为0,表示为+Infinity

例如:0 11 1111 1111 00000……0000  表示的是 +infinity即正无穷

  • 负无穷

符号为1 指数为2047, 尾数为0, 表示为-Infinity

例如:1 11 1111 1111 00000……0000 表示的是 -infinity即负无穷

  • 非数字NaN

指数为2047,尾数不全为0,表示NaN。

例如:0 11 1111 11111 0100……0100 表示NaN 即非数字

总结

通过以上对JavaScript的数字存储的大概理解,我们现在就可以清楚的知道为什么0.1 +0.2 为什么不等于0.3了

原因是在存储数字时存储空间是有限的,对于无限小数会被转换成1.xxx * 2^n次方的然后只会保留52位小数,这样就会造成数字的误差,在十进制0.1和0.2转换成二进制的过程中都是无限小数,所以在使用双浮点数储存时不是正常的0.1和0.2,而是一个近似值。


互动

在你清楚的了解了这个JavaScript的数字存储之后,也认识到了JavaScript的数字存储是不精准的。您肯定会产生许许多多的问题例如以下问题

  • JS中表示的最大数字是多少?

答案:请您在微信公众号回复, JS中表示的最大数字是多少? 获取

  • JS中能表示的数字有效位数是多少?

答案:请您在微信公众号回复,JS中能表示的数字有效位数是多少? 获取

  • JS中表示的整数是连续的吗?

答案:请您在微信公众号回复, JS中表示的整数是连续的吗? 获取

有其他问题可以在公众号直接留言哦。

学完了,给自己放首歌,奖励一下,听一听。

薛之谦 _ 毛不易 - 消愁 (Live)00:0002:53未加入话题


长按二维码关注天天学前端,每次更新不错过。

天天学前端

天天学前端

在这里我们分享WEB前端的相关技术文章、学习资源、热点资讯、面试难点等内容。期待你的建议和指正。 期待和你在前端的世界中一起学习,获得更多成长!

公众号

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值