Java中float和double两种基本浮点类型的浮点数存在精度缺失问题(不只是Java,其他语言也是),所以一般在对精度要求较高的计算中(如金融中货币金额的计算)是绝对不可以使用float和double进行精确计算,需要使用BigDecimal进行计算。
有大量的构造器和方法可以用于创建BigDecimal对象,这里就不一一列举了,可以查看API文档或源码,归纳来说主要分3类:
1、BigDecimal(double val)
这个是不推荐使用的,因为使用该构造器有一定的精度问题,如new BigDecimal(0.3),实际传入构造器的并不是0.3,而是一个接近的值。当然一般计算后再转回double类型可能结果没问题精度未确实,但是并不能成为使用的理由,这是个危险的做法,不要使用。
2、BigDecimal(String val)
建议优先使用基于String的构造器,因为不会有精度问题,new BigDecimal("0.3"),实际传入的就是0.3。
通过接口传递金额等参数时,最好也使用String类型,不要使用float和double两种基本浮点类型,可以避免精度缺失。
3、BigDecimal valueOf(double val)
如果确实要使用double类型,不要使用对应的构造器,就使用valueOf静态方法来创建BigDecimal对象。
BigDecimal 提供了一系列的对精确浮点数进行计算的方法,可以用来精确计算,还有将计算结果重新转换成其他类型数据的方法,详情见API文档或源码,这里提供一个例子,一般一些公司会自己封装工具类或者使用第三方jar包中的工具类进行计算。
如下是个BigDecimal计算的工具类的简单例子(可以根据需要进行调整)。
/**
* @author zhangbo
* @date 2019/5/9 21:16
* @since 1.0
*/
public final class BigDecimalUtil {
/**
* 默认除法运算精确度
*/
private static final int DEFAULT_DIV_DEFINITION = 2;
/**
* 加法
* @param num1 数字1
* @param num2 数字2
* @return 和
*/
public static String add(String num1, String num2) {
BigDecimal bigDecimal1 = new BigDecimal(num1);
BigDecimal bigDecimal2 = new BigDecimal(num2);
return bigDecimal1.add(bigDecimal2).toString();
}
/**
* 加法
* @param num1 数字1
* @param num2 数字2
* @return 和
*/
public static double add(double num1, double num2) {
BigDecimal bigDecimal1 = BigDecimal.valueOf(num1);
BigDecimal bigDecimal2 = BigDecimal.valueOf(num2);
return bigDecimal1.add(bigDecimal2).doubleValue();
}
/**
* 减法
* @param num1 数字1
* @param num2 数字2
* @return 减法的结果
*/
public static double subtract(double num1, double num2) {
BigDecimal bigDecimal1 = BigDecimal.valueOf(num1);
BigDecimal bigDecimal2 = BigDecimal.valueOf(num2);
return bigDecimal1.subtract(bigDecimal2).doubleValue();
}
/**
* 乘法法
* @param num1 数字1
* @param num2 数字2
* @return 乘法的结果
*/
public static double multiply(double num1, double num2) {
BigDecimal bigDecimal1 = BigDecimal.valueOf(num1);
BigDecimal bigDecimal2 = BigDecimal.valueOf(num2);
return bigDecimal1.multiply(bigDecimal2).doubleValue();
}
/**
* 除法
* @param num1 除数
* @param num2 被除数
* @param definition 精确度
* @return 乘法的结果
*/
public static double divide(double num1, double num2, Integer definition) {
BigDecimal bigDecimal1 = BigDecimal.valueOf(num1);
BigDecimal bigDecimal2 = BigDecimal.valueOf(num2);
int scale = null == definition ? DEFAULT_DIV_DEFINITION : definition;
return bigDecimal1.divide(bigDecimal2, scale).doubleValue();
}
public static void main(String[] args) {
double d1 = 0.1;
double d2 = 0.3;
System.out.println("使用double作为构造器参数:");
BigDecimal bigDecimal1 = new BigDecimal(d1);
BigDecimal bigDecimal2 = new BigDecimal(d2);
System.out.println("bigDecimal1 = " + bigDecimal1);
System.out.println("bigDecimal2 = " + bigDecimal2);
System.out.println("d1 - d2 = " + bigDecimal1.subtract(bigDecimal2).doubleValue());
System.out.println("使用String作为构造器参数:" + new BigDecimal("0.3"));
System.out.println("0.1 + 0.3 = " + BigDecimalUtil.add("0.1", "0.3"));
System.out.println("使用BigDecimal.valueOf方法:" + BigDecimal.valueOf(0.3));
System.out.println("0.1 + 0.3 = " + BigDecimalUtil.add(d1, d2));
System.out.println("0.1 - 0.3 = " + BigDecimalUtil.subtract(d1, d2));
System.out.println("0.1 * 0.3 = " + BigDecimalUtil.multiply(d1, d2));
System.out.println("0.1 / 0.3 = " + BigDecimalUtil.divide(d1, d2, 3));
}
}
测试驱动开发,要写好用例,开发,在自测,一般写完代码,就要写对应的junit用例进行验证,确保功能正确性,这里不是实际开发只是练习,直接使用main方法进行快速的验证即可。
结果:
使用double作为构造器参数:
bigDecimal1 = 0.1000000000000000055511151231257827021181583404541015625
bigDecimal2 = 0.299999999999999988897769753748434595763683319091796875
d1 - d2 = -0.19999999999999998
使用String作为构造器参数:0.3
0.1 + 0.3 = 0.4
使用BigDecimal.valueOf方法:0.3
0.1 + 0.3 = 0.4
0.1 - 0.3 = -0.2
0.1 * 0.3 = 0.03
0.1 / 0.3 = 0.3
编者题外话:有幸进入自己喜欢的软件开发行业,编者感觉很荣幸,感谢这一路来各位领导和同事以及朋友们的帮助!工作这些年,一直忙于工作,个人也不够勤快,一直没怎么写博客。本地总结文档管理的也不够好。计划后面多写一些博客,一方面进行学习总结,不断提升自己,方便查阅;另一方面也希望能给他人带来帮助。希望能有志同道合的朋友一起进步,欢迎提出疑问和建议,共同学习讨论和进步。已经入坑,就要砥砺前行,不断学习和进步,总有千般劫难,只当一种快乐的挑战,当你感觉艰难时就是你在进步在突破时!