关于JavaScript中浮点数运算的问题

浮点数运算的问题和原因

问题

在JavaScript中,一些浮点数的运算会出现结果不正确的情况,就像0.1+0.2,它不等于0.3,而是0.30000000000000004,而0.1+0.2==0.3也是false的;再例如1.96*1.96=3.8415999999999997(本来应该是3.8416)。其实这个也不仅仅是 js 才有的问题,其他语言,例如 python 等也会有。

原因

出现这个问题的原因,其实是因为数值的表示在计算机内部是用二进制的。例如,十进制的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了。
想更详细地了解可以看这里:THE FLOATING-POINT GUIDE Basic Answers(可能要翻墙)

解决办法

这里给了 JavaScript 里的解决方法:Floating-point cheat sheet for JavaScript
就是可以引入相应的库来运算:BigDecimal.jsbignumber.js
而对于复杂的表达式,我想应该先将表达式转换成逆波兰式,再将每一个数值都转换成BigDeciamlBigNumber,然后用库里的方法来运算。

上面的方法对于复杂的带有函数的表达式表达式,需要对表达式优先级进行识别等做工作,工作量应该会挺大的, 所以在 Capslock+ 里的计算板中我用了比较偷懒的方式解决:
所有运算结果都经过以下这个函数:

1
2
3
4
5
6
7
8
9
10
11
//小数点后面有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;
}

这样对于精度要求不是很高的运算,至少能给出个准确的结果,而不会出现0.1+0.2=0.30000000000000004,1.96*1.96=3.8415999999999997这种奇怪的结果。
这样验证过这个函数:

1
2
3
4
5
6
7
8
9
var i=0,x=0;
console.log('start',i,x);
console.log('running...');
for(var k=0;k<100001;k++,i+=0.00001){
x=fixFloatCalcRudely(i);
if(x.toString().length>8)
console.log(i,x);
}
console.log('end',i,x);

计算结果的小数位本应小于或等于5位的计算应该都可以得到正确结果。