关于0.1+0.2!=0.3浮点数计算分析与解决方法

出现的原因

最近在做一个计算器,发现0.1+0.2!=0.3,感觉计算机有时候也是有问题的。了解资料才看到小数点在计算机中是以二进制表示,而有些小数用二进制表示是无穷,所以才会出现上面这种精确度的问题。

一些浮点数表示成二进制

十进制           二进制
0.1              0.0001 1001 1001 1001 ...
0.2              0.0011 0011 0011 0011 ...
0.3              0.0100 1100 1100 1100 ...
0.4              0.0110 0110 0110 0110 ...
0.5              0.1
0.6              0.1001 1001 1001 1001 ...

运行一下下面代码

输入                 输出
0.1+0.05==0.15       FALSE
1-0.1-0.1-0.1==0.7   FALSE
0.3/0.1 == 3         FALSE
1.0-0.6 == 0.4       True
1.0-0.5 == 0.5       True
1.0-0.4 == 0.6       True
1.0-0.3 == 0.7       True
1.0-0.2 == 0.8       True

出现这个问题的原因,其实是因为数值的表示在计算机内部是用二进制的。例如,十进制的0.625,换成二进制表示就是0.101(1*2-1+0*2-2+1*2-3)。0.625这个数倒还好,刚好可以准确表示出来。但如果是0.1的话呢,换成二进制就是0.00011(0011无限循环),也就是:0.000110011001100110011001100110011...,位数是无限的,只能取近似。对于这些不能准确表示的数就有可能会出现这个问题。为什么是可能呢?因为有些数的计算结果,例如0.1+0.3,它虽然也是不能精确地表示,但是它结果足够接近0.4,那取了近似后就成了0.4了。

解决方法

方法一:使用github上的库:BigDecimal.jsbignumber.js

方法二:在这篇文章里面找到一个简单的函数。

//小数点后面有6个或以上的0或9,就四舍五入
function fixFloatCalcRudely(num){
    if(typeof num == 'number'){
        var str=num.toString(),
            match=str.match(/\.(\d*?)(9|0)\2{5,}(\d{1,5})$/);
        if(match != null){
            return num.toFixed(match[1].length)-0;
        }
    }
    return num;
}

可以将每次的运算结果赋值给它,从而得到比较精确的结果。

方法三:使用简单点四舍五入方法,其实跟上面的方法差不多,只不过取了一个10位小数。

function numTofixed(num) {
    if (typeof num == 'number') {
        num = parseFloat(num.toFixed(10))
    }
    return num;
}
numTofixed(0.1 + 0.2);

方法二的正则表达式解读:

match=str.match(/\.(\d*?)(9|0)\2{5,}(\d{1,5})$/);
  • match()

  • 在字符串内检索指定的值,找到一个或多个正则表达式的匹配
  • stringObject.match(searchvalue)
  • stringObject.match(regexp)
  • 返回匹配结果的数组,数组内容依赖于regexp是否具有全局标志g。

例如下面这个

var d = '55 ff 33 hh 77 tt'.match(/\d+/g); // d = ["55", "33", "77"]

再看一下表达式里面有一个\2{5,},这个\2表示跟第二个表达式匹配,即跟(9|0)这个相同。涉及到子表达式和捕获,反向引用的概念

op1gW97

  • 一个子表达式是一个整体,可重复后面引用
  • /(\d)(\d)\2\1/gi; 第二位和第三位相同,第一位和第四位相同
  • /(\d)\1(\d)\2(\d)\3(\d)4/; aabbccdd形式的数字
  • /(\d){5}-(\d)\2\2(\d)\3\3(\d)\4\4/gi; 12345-111222333

假如我们使用

fixFloatCalcRudely(0.1+0.2) //[".30000000000000004", "3", "0", "4"]

由于没有加全局,所以会输出全部结果,第一个元素匹配整个字符串,第二个开始与圆括号内的子表达式相匹配的子串结果。由于我们输出只有0.3后面都是0,进入无限循环。

关于正则文章:

  1. 理解正则表达式
  2. javascript正则表达式
  3. 更多用搜索:正则

关注我

我的微信公众号:前端开发博客,在后台回复以下关键字可以获取资源。

  • 回复「小抄」,领取Vue、JavaScript 和 WebComponent 小抄 PDF
  • 回复「Vue脑图」获取 Vue 相关脑图
  • 回复「思维图」获取 JavaScript 相关思维图
  • 回复「简历」获取简历制作建议
  • 回复「简历模板」获取精选的简历模板
  • 回复「加群」进入500人前端精英群
  • 回复「电子书」下载我整理的大量前端资源,含面试、Vue实战项目、CSS和JavaScript电子书等。
  • 回复「知识点」下载高清JavaScript知识点图谱

每日分享有用的前端开发知识,加我微信:caibaojian89 交流