金额计算在一些支付类项目和电商项目中十分常见,很多开发在日常的写代码中时长会使用加减乘除等计算。

包装类型计算问题

可以看到,两个包装类且值都是200的使用 == 的比较的结果都是false,使用equals比较是true。

Integer源码:

通过Integer的源码可以发现当Integer的范围是 -128~127的时候是直接从缓存里面取的返回值,而超过以后是重新new一个对象返回,当然使用==的时候就会是false,Integer 和 int比较的时候包装类会先转成int 再做比较,所以不会出现false的情况了,同时大家可以看到在编译阶段编辑器就提示了有问题。

开发中可以通过 BigDecimal 进行数值类型的计算。下面会说的

多精度计算问题

可以看到有两个计算出现了精度问题 Java中的简单浮点数类型float和double不能够精确运算。这个问题其实不是JAVA的bug,因为计算机本身是二进制的,而浮点数实际上只是个近似值,所以从二进制转化为十进制浮点数时,精度容易丢失,导致精度下降。

可以看到使用double类型除以0,得到 Infinity(无穷大)。原理是因为java的float和double使用了IEEE 754标准。这个标准规定:浮点数除以0等于正无穷或负无穷。

BigDecimal计算

一般java代码中遇到高精度金额计算,日常使用bigDecimal类型。

在使用BigDecimal类来进行计算的时候,主要分为以下步骤:

1、用float或者double变量构建BigDecimal对象。
2、通过调用BigDecimal的加,减,乘,除等相应的方法进行算术运算。
3、把BigDecimal对象转换成float,double,int等类型。

BigDecimal的compareTo()

BigDecimalA > bigDecimalB时,比较结果为 1
BigDecimalA = bigDecimalB时,比较结果为 0
BigDecimalA < bigDecimalB时,比较结果为 -1

常用的加,减,乘,除,BigDecimal类提供了相应的成员方法。

 public BigDecimal add(BigDecimal value);           // 加法
 public BigDecimal subtract(BigDecimal value);      // 减法
 public BigDecimal multiply(BigDecimal value);      // 乘法
 public BigDecimal divide(BigDecimal value);        // 除法

下面是一个工具类,该工具类提供加,减,乘,除运算。

public class Arith {
    /**
     * 提供精确加法计算的add方法
     * @param value1 被加数
     * @param value2 加数
     * @return 两个参数的和
     */
    public static double add(double value1,double value2){
        BigDecimal b1 = new BigDecimal(Double.valueOf(value1));
        BigDecimal b2 = new BigDecimal(Double.valueOf(value2));
        return b1.add(b2).doubleValue();
    }

    /**
     * 提供精确减法运算的sub方法
     * @param value1 被减数
     * @param value2 减数
     * @return 两个参数的差
     */
    public static double sub(double value1,double value2){
        BigDecimal b1 = new BigDecimal(Double.valueOf(value1));
        BigDecimal b2 = new BigDecimal(Double.valueOf(value2));
        return b1.subtract(b2).doubleValue();
    }

    /**
     * 提供精确乘法运算的mul方法
     * @param value1 被乘数
     * @param value2 乘数
     * @return 两个参数的积
     */
    public static double mul(double value1,double value2){
        BigDecimal b1 = new BigDecimal(Double.valueOf(value1));
        BigDecimal b2 = new BigDecimal(Double.valueOf(value2));
        return b1.multiply(b2).doubleValue();
    }

    /**
     * 提供精确的除法运算方法div
     * @param value1 被除数
     * @param value2 除数
     * @param scale 精确范围
     * @return 两个参数的商
     * @throws IllegalAccessException
     */
    public static double div(double value1,double value2,int scale) throws IllegalAccessException{
        //如果精确范围小于0,抛出异常信息
        if(scale<0){
            throw new IllegalAccessException("精确度不能小于0");
        }
        BigDecimal b1 = new BigDecimal(Double.valueOf(value1));
        BigDecimal b2 = new BigDecimal(Double.valueOf(value2));
        return b1.divide(b2, scale).doubleValue();
    }
}

BigDecimal的舍入策略:

ROUND_CEILING:无论后面是多少(后面是0的话除外),在正常值下,往大变
ROUND_FLOOR:无论后面是多少(后面是0的话除外),在正常值下,往小变
ROUND_UP:无论后面是多少(后面是0的话除外),在绝对值的角度,往大变
ROUND_DOWN:无论后面是多少,都直接丢弃
ROUND_HALF_UP:0-4舍,5-9入,在绝对值的角度,舍和入
ROUND_HALF_DOWN:0-5舍,6-9入,在绝对值的角度,舍和入
ROUND_HALF_EVEN:0-4舍,6-9入,;在绝对值的角度舍和入如果是5,则结果为偶数的一边

在开发中为避免计算出现错误,建议使用分来进行存存储,微信的支付单位就分,如果以分为单位就要考虑到数据展示需要转换成 元。