深入理解JavaScript系列:为什么03-0.2不等于0.1

Posted on 2016-05-02 16:14  吃人喵  阅读(3448)  评论(0编辑  收藏  举报

五一宅家看书,所以接着更新一篇文章。

今天讲一下为什么03-0.2不等于0.1这个问题。

有点标题党的味道,在JavaScript中,当你试着对小数进行加减运算时,有时候会发现某个结果并非我们所想的那样,就比如标题中所说的为什么我用0.3去减0.2却得不到0.1?

当我碰到这个问题的时候我一下子也不知道问题到底出在哪,但他实实在在的就给出了一个0.09999999999999998的结论。

其实这个问题的本质原因就是在于计算机对浮点数的处理上,对于计算机的运算方法和数据的表示处理等内容是很大的一块内容,而且这块内容只要是计算机专业的同学在大学期间都已经接触到了而且很可能在你的期末考试试卷上就有这样的一道相关的题目是检测你这个知识点的。所以今天在朋友圈发了一条动态,内容是不好好学习的下场就是你连为什么03-0.2不等于0.1都不知道。

下面从浅显易懂的角度去讲述这个问题。

1.计算机中如何表示数据?

  计算机是个机器,内部所有数据和指令都是以01为基础来实现。也就是计算机的认知水平仅仅局限于二进制,而我们习惯于十进制计数规则,所以这中间就牵扯到一个各种数制之间的转换的问题。数制有二进制、八进制、十进制和十六进制,当然只要你高兴,搞个三进制五进制也不是不可以,但是这几个进制是我们约定俗成和被广泛使用的。

2.各数制间如何进行转换?

  真的不怎么想写这个问题,因为能回答这个问题的答案在你的课本里边早就有很详细且很全面的讲解,如果你浮躁到只想知道结果而自己不懂却连课本都不想翻的地步,那么这篇文章其实也没必要看了。

  但是为了与下面内容更好的连贯性,这里还是简单讲一下怎么从十进制转换到二进制的过程。

  十进制转换为二进制:

  比如11(10)这个十进制数,我们转换成二进制为11(10)=8+2+1=11=23+21+20=1000+10+1=1011(2);如果看不懂这个转换,那你去翻课本吧,这里就不列那些公式什么的了。 

  二进制转换为十进制:

  二进制转换为十进制也就是十进制转换为二进制的一个逆向过程,还是拿11(10)这个数字举例。1011(2)=1000+10+1=23+21+20=8+2+1=11(10)。

3.0.3-0.2不等于0.1和12条问题有什么关系?

  第一条说了因为我们习惯于非二进制,而计算机只能认知二进制,所以我们在编程语言中所写的那些以十进制表示数值的代码,对于底层的计算机实现来说是按照二进制来进行的。所以这其中肯定就会有一个进制转换的问题。

  当我们在高级语言里用十进制书写代码,到最后得到一个正确的依然是十进制的结果的这个过程,计算机在中间进行了两次数制转换,一次是十进制转二进制,转换完后进行数据操作,操作完成后所得到的二进制数据又逆向从二进制转换为十进制,最后呈现给我们一个我们所预期的结果。

  0.3-0.2不等于0.1的原因就出现在这个过程当中。

4.那么0.3-0.2不等于0.1到底是怎么形成的?

  紧接着上边的表述往下讲,以0.3-0.2为例,在JavaScript语言中完成这一个计算过程。

  首先我们打出0.3-0.2的表达式,本想我们很轻易的得出一个0.1的结果,然而,事实出乎意料,结果却给我们返回一个0.09999999999999998。

  数据在计算机中的分类大体为符号数和无符号数、定点数和浮点数,相互交叉便可以分为无符号定点数、浮点数和有符号定点数、浮点数。0.3(10)转换成二进制数据表示为:0.3(10)=2-3+2-4+2-7+2-8+......=0.001100110011001100110011001100110011001100110011001101(2),在JavaScript可以用对象的toSting方法查看,如0.3.toString(2);然后我们拿到这个转换后的二进制数据再按照单精度float或者双精度double的格式规格化成符号位阶数尾数的浮点数的表示形式,这就完成了从十进制到二进制表示的转换过程。

  再然后计算机就将0.3和0.2的二进制浮点数形式的表示进行运算,很显然,这个结果跟我们所想的0.3-0.2就该等于0.1可能会有些差异,因为我们在运算的过程中要对数据进行对阶操作,即两个数的阶数不同的话要调整到统一大小的阶数,调整过程中一般是右移,右移过程中就会出现精度受损的可能,因此计算完后的结果很有可能就不是我们所想要的那个结果了。

5.0.3-0.2就一定不等于0.1吗?

  首先,只要是小数,或者说是浮点运算,就有可能会出现精度受损的情况,但是只是可能,也不是说就一定会出现精度受损。具体要看操作数在进制转换过程中和二进制数值运算过程中有没有受到精度的影响,你比如0.5-0.25=2-1-2-2=2-2=0.25,这种情况下就不会出现精度受损,所以结果和我们预期的是一样的。

6.怎么规避这个问题?

  一切问题都可以解决,前提是我们知道了原因。现在我们知道了为什么0.3-0.2不等于0.1,那就反方向考虑解决问题的方法。那就是避免他们在数制转换过程和二进制数值运算过程中精度受损的影响。比如,我有3毛钱,也就是0.3元,然后买了只雪糕花掉我2毛钱,也就是0.2元,问还剩多少钱?如果是在以前,我们肯定不假思索的0.3-0.2=0.1想当然的就得出了结论。但是现在我们已经认识到这样会有问题,那么我们怎么避免他呢?那就是我为什么非要用元作单位而不用毛或者用分作单位?如果我用毛作单位3-2=1,用分作单位30-20=10就一定不会出现问题。因为整数的运算肯定不会有浮点运算的精度问题。所以这就是解决问题的思路。

 

完!

祝愉快。