大概所有学过计算机的同学都应该具备这样的基本常识:
1、0.1+0.2 不一定等于0.3(1-0.99同理不一定等于0.01)
2、1/2不一定等于0.5
3、1/3 *3不一定等于1
4、log2+log5也不一定等于1
可是,我其实也经常忘记掉。。大概教java的刘骥老师看到我这个问题,也被气死了吧。
这是java计算的结果:
这是python计算的结果:
这是JS计算结果:
那原因到底是什么呢?问题又该如何解决?
首先应该明确的是,只要两个数字的加减乘除,只要不是int类型,那么都有可能存在精度丢失的问题(这里暂且不说当两个不同类型的数进行基本运算符操作时,精度小的数值类型首先会向精度大的数值类型进行转换,最后进行计算的问题。)
原因简化如下:因为不管是float还是double类型,他们的精度都是有限的,假如超过了他们所能表示的范围,那么就会产生误差,其结果只能是接近而不是等于我们想要的结果。(因为计算机最后都是二进制表示的,(好的,这是一句废话。)
下面举个例子:上述的0.3为什么不能正确显示出来。
比如整数11,二进制是这样表示的:
11/2=5+1 -->1
5/2=2+1 -->1
2/2=1+0 -->0
1/2=0+1 -->1
所以11的二进制表示为:1011.
然后我们来看下0.3的二进制:
0.3*2=0.6 -->0
0.6*2=1+0.2 -->1
0.2*2=0.4 -->0
0.4*2=0.8 -->0
0.8*2=1+0.6 -->1
你会发现这样会不断循环下去。。。。所以这时候就不能很精确的表示了,也就出现的精度丢失的问题。就如同十进制系统中能不能准确表示出1/3一样,大概就是这样。
所以一般大家都建议使用java.math.BigDecimal。能够完全自己控制精度,结果也准确。但是需要注意的是,“直接使用字符串来构造BigDecimal是绝对没有精度损失的,如果用double或者把double转化成string来构造BigDecimal依然会有精度损失,所以我觉得这种解决方法就是在数据库中就把浮点数用string来表示存放,涉及到运算直接用string构造double,否则肯定会有精度损失。” 不过BigDecimal也有缺点,就是效率不如double。
如下,就是我们想要的结果:
后端处理好了之后,我们会发现前端也会有这样的问题。
就比如我们碰到的这个问题。我们所见的不一定就是所得。所以前后端对于这个数据都需要做一定的处理,不然你会发现。明明输入的数据很正常,但是所得到的结果却往往不如意。
后来大概是先前端使用tofixed()函数四舍五入保留了4位小数,然后给后端,后端再处理。
但是,总感觉使用这个tofix哪里肯定还是会出问题的。。。没在深究了。。。