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

Category:
发表:

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

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

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

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

console.log(0.1 + 0.2);

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

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

> console.log(0.1 + 0.2)
> 0.30000000000000004

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

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

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

那么0.1和0.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

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

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,按照第一种思路,我们可以这么来做,

var a = 0.1;
var b = 0.2;

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

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

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

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 = ? 时,小心他在给你挖坑哦😎