最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 0.1 + 0.2为什么不等于0.3?

    正文概述 掘金(白泽战天下)   2021-04-06   979

    在弄清楚这个问题的过程中经历了不少的思想斗争,要弄清楚这个问题,还要有一些计算机底层原理的知识。

    首先,要明确程序中的所有计算,转到计算机底层都是二进制计算,而且,二进制计算是没有减法的概念的,减法会转成加一个负数进行计算。

    还需要注意一点,0.1+0.2不等于0.3这个问题不仅仅在JavaScript中存在,在其他遵循IEEE 754规范的编程语言中也都存在这个问题。JavaScript 中所有数字包括整数和小数都只有一种类型 — Number。它的实现遵循 IEEE 754 标准,使用 64 位固定长度来表示,也就是标准的 double 双精度浮点数(相关的还有float 32位单精度)。

    回到问题本身,我们先来阐述如何把0.1转换成计算机底层存储的样子,0.2的转换的思路也就清楚了。关于如何把十进制小数转换成二进制的方法,参考菜鸟教程即可,此处不再赘述。0.1转换为二进制的过程是这样的:

    0.1 + 0.2为什么不等于0.3?

    到后面你会发现,这个处理过程是一个无限循环的状态。先不用管它,将其先记录下来:

    接下来是应该处理无限循环了吗?并不是,我们知道,在日常的十进制运算中,我们为了表示尽可能大的数据,会采用科学记数法。计算机底层也是同样的道理,前面我们提到,JavaScript中以双精度存储Number类型,因此如果将数据实打实地转换成二进制后直接存储,那么它能存储的最大数是263(第一位是符号位),在数据量级发达的今天,这个可计算的数值范围是相当有限的。所以,二进制的数据也要转换成科学记数法,然后对科学记数法中进行分部存储:

    OK,接下来的问题是,计算机的64位是怎样来存储科学记数法的呢?

    0.1 + 0.2为什么不等于0.3?

    这张图标示了64位二进制不同部分存储的数据标示。首先,最高一位为符号位,因此它无法参与存储数据的重任,其后的11位(指数部分)用于存储科学记数法中指数的二进制数,剩余的52位(尾数部分)用于存储科学记数法中尾数小数点后52位。这三部分中的符号位和尾数部分都很好明确,符号位0表示正数,1表示负数;尾数部分就是刚刚科学记数法中尾数的小数部分前52位;指数部分的确定又要复杂一点。

    内存中给出了11位二进制给指数,因此,11位二进制转换成十进制的话,能存储的数据范围就是:[0, 211],即[0, 2048]。但是还有个问题,指数还有可能是负数!负数怎么表示?难道11位又要让出一位来表示符号位?并不是,在这部分的处理中,IEEE754标准将指数为0时的基数定为1023(以1023作为正负数的分界线),相当于能存储[-1023, 1024]这个范围的数。因此,以0.1转换为科学记数法后的指数为例,指数-4会被转换成1023 - 4 = 1019,然后再转换成二进制: 0.1 + 0.2为什么不等于0.3?

    1019转换成二进制后为:1111111011。因此,0.1最终二进制的前12位为:

    接下来,尾数部分只保留52位,无限循环怎么处理?此时要进行舍入运算,遵循0舍1入的原则(类似于十进制中的四舍五入的意思),这也是本问题的关键点所在,舍入运算使数据丢失了些许精度。

    你也许会疑惑,一个简单的计算都能导致计算结果的偏差,那么稍微复杂一点的运算结果还可靠么?当然可靠。其一,像这种转换时出现无限循环的场景并不多见,即使有,计算机提供的双精度存储也能够保证数据运算在允许的误差范围内;其二,计算机底层有对精度误差运算的处理机制,不会允许计算在偏差的路上越走越远。

    0.1最终的二进制形式是这样的:

    0.2的二进制存储为:

    接下来就是做加法运算,二进制加法并不难,和十进制类似。然而事情并不简单,在我们将0.1和0.2转为科学记数法时,我们发现0.1的指数是-4,0.2的指数是-3。要想将他们运算的结果也采用科学记数法的方法表示,就得将指数统一然后提取公因数进行计算。这里就涉及到一个对阶运算,为了尽可能减小精度损失,需要遵守小阶对大阶(即将较小的指数转换为较大的指数)的原则。在这个问题中,我们要将指数统一成-3。因此,0.1在经过对阶操作后的二进制,是这样的:

    尾数需要向右移一位,右移超出的部分进行舍入运算。默认省略的整数部分的1被移到小数部分了,因此整数部分变成了0。

    接下来做加法的时候,要将尾数前面省略的整数部分补全,因为存在进位的时候,整数部分是有可能变化的,这也是为了方便后续调整运算结果。

    尾数相加后的结果为:

    0.1 + 0.2为什么不等于0.3?

    这个结果有两个问题:

    1. 不符合科学记数法的规则。
    2. 尾数部分存在超出位数的情况。

    因此要对结果做出调整,首先将结果变为“1.”开头的,即小数点向左移一位,变成:

    同时,要将指数加1:变成:

    最后,依然根据0舍1入的原则,将尾数部分超出52位以外的部分做舍入运算,结果为:

    最终的完整结果为:

    转换为十进制即为:

    至此,这个问题的详细描述和分析就告一段落了。

    总结:

    1. JavaScript小数在计算机底层以双精度(即64位)的方式存储。
    2. 整个过程存在多处精度损失:对阶运算、加法运算。
    3. 精度损失实质上舍入运算的结果,涉及到舍入运算都要遵循0舍1入的原则。

    起源地下载网 » 0.1 + 0.2为什么不等于0.3?

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元