Javascript中浮点数的计算精度问题

Category:
发表:

如果我问你 0.1 + 0.2 = ?你可能会觉得我是个傻逼。因为这个连学龄前的小朋友可能都知道答案呢。

在现实生活中0.1 + 0.2 = 0.3,这当然没什么问题的。但是在程序语言中,或者说计算机世界中,可就不一定了。

不信?那我们来做几个实验。

来个简单的javascript片段,就简单的使用上面的问题吧,

1
console.log(0.1 + 0.2);

这行代码的输出是什么呢?0.3?

其实并不是,我们来看一下这句代码在chrome的dev tools中的输出是什么,

1
2
> console.log(0.1 + 0.2)
> 0.30000000000000004

好奇怪的结果,怎么会是0.30000000000000004呢?难道是Javascript语言的bug还是chrome dev tools的bug?

其实,这不是语言的bug或者宿主环境的bug。目前所有的程序设计语言在对浮点数进行四则运算时,都会涉及到浮点数精确度的问题。

我们知道在计算机的世界中,计算机只认识0,1,我们传入的十进制数字并不会被计算机直接识别。计算机会先将其转换成二进制,然后使用转换后的二进制进行计算。

那么0.1和0.2转换成二进制分别是,

1
2
(0.1) => 0.0001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 101
(0.2) => 0.0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 01

然后对上面的两个二进制数字做加法,得到的结果是,

1
0.0100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1101 01

再把这个二进制转换成十进制,就是我们前面的结果0.30000000000000004了。

这里提一下,我是通过这个工具进行数制转换的,得到的结果其实是被截断过的。

计算机世界的数值计算基本上都是这个过程,只不过C++、C#、Java这些传统强类型语言将这个边界问题封装在内部了,它们在进行浮点数四则运算时,会在语言层面自动解决这个问题。而Javascript作为一门弱类型的语言,它在语言层面并没有对这个问题进行处理,所以需要我们手动去处理。

那么,我们如何去避免这个问题呢?

基本上有两种思路。

  • 先扩大数值到javascript可精确识别的精度级别(比如个位数级别)然后再进行计算,然后再对结果除以放大的倍数。
  • 在运算的过程中通过toFixed()指定运算的精度要求。

比如我们要计算0.1 + 0.2,按照第一种思路,我们可以这么来做,

1
2
3
4
5
var a = 0.1;
var b = 0.2;

var ret = (a * 10 + b * 10) / 10;
console.log(ret);

这里,我们在计算之前,现将ab乘以10(放大10倍,当然放大100、1000倍也是可以的),此时ab已经不再是浮点数了,整数的四则运算当然是不需要考虑精度问题啦。最后我们还需要除以之前放大的倍数,得到正确的最终结果。其实这是一种迂回的办法来规避掉浮点数的四则运算精度问题。

按照第二种思路,我们可以这么来做,

1
2
3
4
5
var a = 0.1;
var b = 0.2;

var ret = (a + b).toFixed(1);
var ret = parseFloat(ret);

注意toFixed()返回的是一个String,所以我们还需要进行parseFloat操作。

所以,以后有前端的面试官问你 0.1 + 0.2 = ? 时,小心他在给你挖坑哦😎