前言

相信很多小伙伴在做开发的过程中都遇到过下面这种情况,对两个数进行计算,其中一个或者两个都是小数,结果出现xxx.99999999999或者xxx.000000001这种奇怪的现象。

举个例子

  • 在JAVA中
int a = 2000;
double b = 1999.9;
double c = a-b;
System.out.println(c);
//输出0.09999999999990905
  • 在PHP中
$a = 2000;
$b = 1999.9;
$c = $a-$b;
echo $c;
//输出0.099999999999909
  • 在JS中
console.log(2000-1999.9)
//输出0.09999999999990905

在其它语言中也会有同样的问题,各位小伙伴可以自行测试,此处提供一个在线测试平台

问题分析

出现此问题的原因其实是在操作系统计算时要先将十进制转换成二进制之后再进行计算。关于操作系统如何处理二进制小数可阅读浮点数(floating-point number)二进制存储格式这篇文章。 再看上述例子中1999.9这个数字的小数部分0.9,我们接下来尝试将0.9转换成二进制格式(采用乘2取整法):

  • 0.9*2=1.8 整数位:1 小数位:8
  • 0.8*2=1.6 整数位:1 小数位:6
  • 0.6*2=1.2 整数位:1 小数位:2
  • 0.2*2=0.4 整数位:0 小数位:4
  • 0.4*2=0.8 整数位:0 小数位:8
  • 0.8*2=1.6 整数位:1 小数位:6
  • 0.6*2=1.2 整数位:1 小数位:2
  • 0.2*2=0.4 整数位:0 小数位:4 .....

相信大家已经看出上面已经构成了一个死循环,小数位始终不能成为0,但是计算机存储浮点数的大小是有限的,单精度最多32bit,双精度64bit,但是这里有无限位,最多也只能保留64位,到这里大家明白了为什么上面的例子会得出 0.099999999999909 这个值了吧,那问题来了,这个问题怎么解决呢?

解决方案

对于这种问题,我经常用下面这两种解决方案:

  • 方式一:将要计算的小数乘100转为整数再进行计算,得到结果后再除100

  • 方式二:

  • JAVA: 使用java.math.BigDecimal类进行计算

  • PHP: 使用BCMath扩展函数进行计算

  • VUE: 使用mathjs插件进行计算,或者对计算结果进行toFixed(2)