为什么Js中 0.1+0.2 不等于 0.3 ?

更新日期: 2019-09-27阅读: 2.4k标签: 运算

在 js 中进行数学的运算时,会出现0.1+0.2=0.300000000000000004的结果,一开始认为是浮点数的二进制存储导致的精度问题,但这似乎不能很好的解释为什么在同样的存储方式下0.3+0.4=0.7可以得到正确的结果。本文主要通过浮点数的二进制存储及运算,和IEEE754下的舍入规则,解释为何会出现这种情况。


一、浮点数的二进制存储

JavaScript遵循IEEE754标准,在64位中存储一个数据的有效数字形式。


其中,第0位为符号位,0表示正数1表示负数;第1到11位存储指数部分;第12到63位存小数部分(尾数部分)(即有效数字)。由于二进制的有效数字总是表示为 1.xxx…的形式,尾数部分在规约形式下的第一位默认为1,故存储时第一位省略不写,尾数部分f存储有效数字小数点后的xxx...,最长52位。因此,JavaScript提供的有效数字最长为53个二进制位(尾数部分52位+被省略的1位)。
以0.1、0.2、0.3、0.4和0.7的二进制形式为例:

0.1->0.0001100110011...(0011无限循环)->0-01111111011-(1 .)1001100110011001100110011001100110011001100110011010(入)
0.2->0.001100110011...(0011无限循环)->0-01111111100-(1 .)1001100110011001100110011001100110011001100110011010(入)
0.3->0.01001100110011...(0011无限循环)->0-01111111101-(1 .)0011001100110011001100110011001100110011001100110011(舍)
0.4->0.01100110011...(0011无限循环)->0-01111111101-(1 .)1001100110011001100110011001100110011001100110011010(入)
0.7->0.101100110011...(0011无限循环)->0-01111111110-(1 .)0110011001100110011001100110011001100110011001100110(舍)

对于52位之后进行舍入运算,此时可看作0舍1入(具体舍入规则在第三部分详细说明),有精度损失。


二、对阶运算

由于指数位数不同,运算时需要进行对阶运算。对阶过程略,0.1+0.2与0.3+0.4的尾数求和结果分别如下:

0.1+0.2->10.0110011001100110011001100110011001100110011001100111
0.3+0.4->10.1100110011001100110011001100110011001100110011001101

求和结果需规格化(有效数字表示),右规导致低位丢失,此时需对丢失的低位进行舍入操作:

0.1+0.2->1.00110011001100110011001100110011001100110011001100111->1.0011001100110011001100110011001100110011001100110100(入)
0.3+0.4->1.01100110011001100110011001100110011001100110011001101->1.0110011001100110011001100110011001100110011001100110(舍)

即:

00111->0100
01101->0110

此处同样有精度损失。在这里我们可以发现,0.3+0.4对阶阶运算且规格化后的运算结果与0.7在二进制中的存储尾数相同(可对照尾数后几位),而0.1+0.2的运算结果与0.3的存储尾数不同,且0.1+0.2转化为十进制时结果为0.300000000000000004。
此时,虽然0.1+0.2与0.3+0.4进行舍入操作的近似位都为1,但一入一舍导致计算结果与“标准答案”的异同。


三、IEEE754标准下的舍入规则

维基百科对最近偶数舍入原则的解释如下:舍入到最接近,在一样接近的情况下偶数优先(Ties To Even,这是默认的舍入方式),即会将结果舍入为最接近(精度损失最小)且可以表示的值,但是当存在两个数一样接近的时候,则取其中的偶数(在二进制中是以0结尾的)。

首先要注意的是,保留小数不是只看后面一位或者两位,而是看保留位后面的所有位。


如图,可以看到近似需要看三位,保留位(近似后的最低位)、近似位(保留位的后一位)、粘滞位(sticky bit 近似位后的所有位进行或运算后看作一位)。
当粘滞位为1时,舍入规则可以看作0舍1入,近似位为0舍,近似位为1入(即第一部分小数二进制存储为52位尾数时所进行的舍入操作)。
当粘滞位为0时,若近似位为0则舍去。
当粘滞位为0时,若近似位为1,无论舍入精度损失都相同,故需取舍入两种结果中的偶数:保留位为1时入,保留位为0时舍(即第二部分对阶运算规格化时的舍入操作)。


四、总结思考

由于IEEE754标准,这样的“bug”不止在JavaScript中会出现,在所有采用该标准的语言中都会存在,实际编程中可以通过设置精度保留位数等方式解决。

本文首发于 vivo互联网技术 微信公众号 
链接:https://mp.weixin.qq.com/s/2kea7-jACCJmSYBQAwXyIg
作者:刘洋 

 

链接: https://www.fly63.com/article/detial/6148

js除了Math.floor方法,还可以通过位运算|,>>实现向下取整

我们都知道通过Math.floor()方法可实现数值的向下取整,得到小于或等于该数字的最大整数。除了Math.floor方法,还可以使用位运算|,>>来实现向下取整哦

es6 扩展运算符 三个点(...)

扩展运算符( spread )是三个点(...)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。

巧用JS位运算

位运算的方法在其它语言也是一样的,不局限于JS,所以本文提到的位运算也适用于其它语言。位运算是低级的运算操作,所以速度往往也是最快的

js中使用位运算,让执行效率更高

平常的数值运算,其本质都是先转换成二进制再进行运算的,而位运算是直接进行二进制运算,所以原则上位运算的执行效率是比较高的,由于位运算的博大精深,下面通过一些在js中使用位运算的实例

js各种取整方式及方法_四舍五入、向上取整、向下取整

js实现:四舍五入、向上取整、向下取整等方法。parseInt、Math.ceil、Math.round、Math.floor、toFixed等的使用

JavaScript循环计数器

JS经常会遇到延迟执行的动作,并且失败后自动尝试,尝试N次之后就不再尝试的需求,今天刚好又遇到,于是写个闭包,以后不断完善继续复用。检查并计数第一个参数用来标记是尝试哪个动作的,第二个参数是最大尝试次数

js 位运算符_js按位运算符及其妙用

大多数语言都提供了按位运算符,恰当的使用按位运算符有时候会取得的很好的效果。在我看来按位运算符应该有7个:& 按位与、| 按位或、^ 按位异或、~ 按位非

PHP取整、四舍五入取整、向上取整、向下取整、小数截取

PHP取整数函数常用的四种方法:1.直接取整,舍弃小数,保留整数:intval(); 2.四舍五入取整:round(); 3.向上取整,有小数就加1:ceil(); 4.向下取整:floor()。

JavaScript 中的相等操作符 ( 详解 [] == []、[] == ![]、{} == !{} )

ECMAScript 中的相等操作符由两个等于号 ( == ) 表示,如果两个操作数相等,则返回 true。相等操作符会先转换操作数(通常称为强制转型),然后比较它们的相等性。

关于js开发中保留小数位计算函数(以向上取整或向下取整的方式保留小数)

前端工作中经常遇到数字计算保留小数问题,由于不是四舍五入的方式不能使用toFixed函数,本文采用正则表达式匹配字符串的方式,解决对数字的向上或向下保留小数问题:

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!