原理
浮点数会有精度损失,如下代码所示
public class NumTest {
public static void main(String[] args) {
double a =1;
double b =0.99;
System.out.println(a-b);
}
}
这段代码运行结果很简单,不是0.01么?!
在这一天之前如果问我结果是什么我也会毫不犹豫的答道0.01,然而真实的结果是:0.010 00000 00000 00009。
虽然运算结果不太对,但是这个结果和0.01相差不大会产生影响么?那看下面这一段代码:
public class NumTest {
public static void main(String[] args) {
double a =1;
double b =0.99;
System.out.println(a-b);
if((a-b) == 0.01){
System.out.println("1 - 0.99 == 0.01");
}else{
System.out.println("1 - 0.99 != 0.01");
}
}
}
输出的结果也在意料之中:1 - 0.99 != 0.01
如果在程序中直接使用double会造成精度损失,极有可能对造成一些莫名奇妙的bug。
但是所有的浮点数都会有精度损失么?
package sort;
public class NumTest {
public static void main(String[] args) {
double a = 0.5;
double b = 0.25;
System.out.println(a-b);
if((a-b) == 0.25){
System.out.println("no problem");
}else{
System.out.println("has problem");
}
}
}
输出的结果是:no problem,也就是说double类型的0.5和0.25在运算的时候没有出现精度损失。
关于精度损失的原理可以很简单的讲,首先一个正整数在计算机中表示使用01010形式表示的,浮点数也不例外。
比如11,11除以2等于5余1
5除以2等于2余1
2除以2等于1余0
1除以2等于0余1
所以11二进制表示为:1011.
double类型占8个字节,64位,第1位为符号位,后面11位是指数部分,剩余部分是有效数字。
正整数除以2肯定会有个尽头的,之后二进制还原成十进制只需要乘以2即可。
举个例子:0.99用的有效数字部分,
0.99 * 2 = 1+0.98 –> 1
0.98 * 2 = 1+ 0.96 –> 1
0.96 * 2 = 1+0.92 – >1
0.92 * 2 = 1+0.84 – >1
……………
这样周而复始是没法有尽头的,而double有效数字有限,所以必定会有损失,所以二进制无法准确表示0.99,就像十进制无法准确表示1/3一样。
解决方案
一般遇到这种需要用到浮点数运算的地方都可以使用java.math.BigDecimal。
首先需要注意的是,直接使用字符串来构造BigDecimal是绝对没有精度损失的,如果用double或者把double转化成string来构造BigDecimal依然会有精度损失,所以我觉得这种解决方法就是在数据库中就把浮点数用string来表示存放,涉及到运算直接用string构造double,否则肯定会有精度损失。
package sort;
import java.math.BigDecimal;
public class NumTest {
public static void main(String[] args) {
String a = "301353.0499999999883584678173065185546875";
double c = 301353.0499999999883584678173065185546875d;
BigDecimal sa = new BigDecimal(a);
BigDecimal sc = new BigDecimal(String.valueOf(c));
BigDecimal dc = new BigDecimal(Double.toString(c));
System.out.println("sa : "+ sa);
System.out.println("sc : "+ sc);
System.out.println("dc : "+ dc);
}
}
上述代码的输出结果是:
sa : 301353.0499999999883584678173065185546875
sc : 301353.05
dc : 301353.05
所以最好的方法是完全抛弃double,用string和java.math.BigDecimal。