Android中数值计算的精度
在平常的开发中,会经常进行数值的计算,而如何才能更加准确的得到计算结果是最重要的,最近在做一个金融类的项目,其中涉及到黄金的计算,所以写这篇文章来总结一下,如何对数值的计算更加精确完美,这是本人在简书的第一篇文章,欢迎大家留言讨论。
- 问题起源:
作为一名利用Java的开发人员,当遇到数值计算的时候首先想到的自然就是Double和Float,Double的精度值是16位,Float的精度值为8位,对于精度要求不是足够高的话完全是够用了。本人开始就是采用Double类型的对黄金的克重以及总额来进行计算的,但是在测试中发现了问题,数值小的时候还正确,但是当数值越大差值也就明显了。
for example:
double a = 11540.0;
double b = 0.855;
Double double1 = a* b;
System.out.println("double=="+double1);
打印结果:double==9866.699999999999
其实这两个数字的乘积是整数:9866.7,其实在Java中为什么会总是出现double值的乘积总是在一个正确的结果左右偏0.0000**1,这是因为当两个double数值相乘时,底层采用转换成二进制来进行乘法的运算,由于在乘完之后在包含小数的二进制中无法完全转换成十进制才会发生这种情况。
- 解决办法
Java中有一个类是BigDecimal,该类是专门计算一些要求精度很高的算法,常用于银行金融类的计算,BigDecimal一共有4个够造方法,我们不关心用BigInteger来够造的那两个,那么还有两个, 它们是:
BigDecimal(double val)
Translates a double into a BigDecimal.
BigDecimal(String val)
Translates the String repre sentation of a BigDecimal into a BigDecimal.
但是在测试中直接将Double值传进去,也就是调用第一个构造方法来创建的BigDecimal也会损失精度,具体这里先不说了,(完了再看一下源码),因此本人只是利用第二个构造函数来进行对BigDecimal来创建对象的,下面是我写的一个工具类:
/**
*
* 作者: 卢卫成 时间: 2017年9月2日 功能描述: 用于对数值的精确计算工具类
*
*/
public class CalcUtils {
public static final int TYPE_ADD = 0x00; // 加法
public static final int TYPE_MULTIPLY = 0x01; // 乘法
public static final int TYPE_DIVIDE = 0x02; // 除法
public static final int TYPE_SUBTRACT = 0x03; // 减法
/**
* 加法
* @param a
* @param b
* @return
*/
public static Double add(Double a, Double b) {
return calc(a, b, -1, TYPE_ADD, null);
}
/**
* 减法
* @param a
* @param b
* @return
*/
public static Double sub(Double a, Double b) {
return calc(a, b, -1, TYPE_SUBTRACT, null);
}
/**
* 乘法
* @param a
* @param b
* @return
*/
public static Double multiply(Double a, Double b) {
return calc(a, b, -1, TYPE_MULTIPLY, null);
}
/**
* 除法
* @param a
* @param b
* @return
*/
public static Double divide(Double a, Double b) {
return calc(a, b, -1, TYPE_DIVIDE, null);
}
/**
* 乘法
* @param a
* @param b
* @param scale 小数点后保留的位数
* @param mode 保留的模式
* @return
*/
public static Double multiply(Double a, Double b, int scale, RoundingMode mode) {
return calc(a, b, scale, TYPE_MULTIPLY, mode);
}
/**
* 除法
* @param a
* @param b
* @param scale 小数点后保留的位数
* @param mode 保留的模式
* @return
*/
public static Double divide(Double a, Double b, int scale, RoundingMode mode) {
return calc(a, b, scale, TYPE_DIVIDE, mode);
}
/**
* 计算
* @param a
* @param b
* @param scale
* @param type
* @param mode
* @return
*/
private static Double calc(Double a, Double b, int scale, int type, RoundingMode mode) {
BigDecimal result = null;
BigDecimal bgA = new BigDecimal(String.valueOf(a));
BigDecimal bgB = new BigDecimal(String.valueOf(b));
switch (type) {
case TYPE_ADD:
result = bgA.add(bgB);
break;
case TYPE_MULTIPLY:
result = bgA.multiply(bgB);
break;
case TYPE_DIVIDE:
try {
result = bgA.divide(bgB);
} catch (ArithmeticException e) {// 防止无限循环而报错 采用四舍五入保留3位有效数字
result = bgA.divide(bgB,3,RoundingMode.HALF_DOWN);
}
break;
case TYPE_SUBTRACT:
result = bgA.subtract(bgB);
break;
}
if (mode==null) {
if(scale!=-1){
result = result.setScale(scale);
}
}else{
if(scale!=-1){
result = result.setScale(scale,mode);
}
}
return result.doubleValue();
}
}
这里涉及到两个参数:Scale和RoundingMode
Scale
scale是用来对利用BigDecimal对数值进行运算后保留的位数。
RouningMode
该参数是BigDecimal是一个枚举类,包含有8个枚举类型,用来说明对经过计算后数值的取舍模式:
- ROUND_UP:远离零方向舍入。向绝对值最大的方向舍入,只要舍弃位非0即进位。
- ROUND_DOWN:趋向零方向舍入。向绝对值最小的方向输入,所有的位都要舍弃,不存在进位情况。
- ROUND_CEILING:向正无穷方向舍入。向正最大方向靠拢。若是正数,舍入行为类似于ROUND_UP,若为负数,舍入行为类似于ROUND_DOWN。Math.round()方法就是使用的此模式。
- ROUND_FLOOR:向负无穷方向舍入。向负无穷方向靠拢。若是正数,舍入行为类似于ROUND_DOWN;若为负数,舍入行为类似于ROUND_UP。
- HALF_UP:最近数字舍入(5进)。这是我们最经典的四舍五入。
- HALF_DOWN:最近数字舍入(5舍)。在这里5是要舍弃的。
- HAIL_EVEN:银行家舍入法。
网友评论