一、Double相加会丢失精度

1、先看案例

public class demo4 {
    public static void main(String[] args) {
        double number1 = 1;
        double number2 = 20.2;
        double number3 = 300.03;
        double result = number1 + number2 + number3;
        System.out.println(result);
    }
}

结果如下:

321.22999999999996

发现结果并不是321.23.

2、原因分析

在使用Java中double 进行运算时,经常出现精度丢失的问题,总是在一个正确的结果左右偏0.0000**1。float和double只能用来做科学计算或者是工程计算,在商业计算中我们要用 java.math.BigDecimal。

3、解决办法:在需要精确的表示两位小数时我们需要把他们转换为BigDecimal对象,然后再进行运算。

  另外需要注意,使用BigDecimal(double val)构造函数时仍会存在精度丢失问题,建议使用BigDecimal(String val)。这就需要先把double转换为字符串然后在作为BigDecimal(String val)构造函数的参数。转换为BigDecimal对象之后再进行加减乘除操作,这样精度就不会出现问题了。这也是为什么有关金钱数据存储都使用BigDecimal。

BigDecimal一共有4个够造方法,我们只考虑两个进行比较,分别是

BigDecimal(double val) 
          Translates a double into a BigDecimal. 
BigDecimal(String val) 
          Translates the String repre sentation of a BigDecimal into a BigDecimal.

建议使用第二个构造方法,原因如下:

修改如下:

public class demo4 {
    public static void main(String[] args) {
        double number1 = 1;
        double number2 = 20.2;
        double number3 = 300.03;
        double result = new BigDecimal(Double.toString(number1))
                .add(new BigDecimal(Double.toString(number2)))
                .add(new BigDecimal(Double.toString(number3))).doubleValue();
//        double result = number1 + number2 + number3;
        System.out.println(result);
    }
}

结果:321.23

二、BigDecimal详解

1、精度控制:

BigDecimal 可以表示精确的十进制数,不会受到浮点数舍入误差的影响。
具有可调整的精度,可以指定小数点后的位数。

2、构造方法:

BigDecimal(double val):将双精度浮点数转换为 BigDecimal。
BigDecimal(String val):将字符串转换为 BigDecimal。

3、常量:

BigDecimal.ZERO:表示值为零的 BigDecimal。
BigDecimal.ONE:表示值为一的 BigDecimal。
BigDecimal.TEN:表示值为十的 BigDecimal。

4、基本算术操作:

add(BigDecimal augend):加法。
subtract(BigDecimal subtrahend):减法。
multiply(BigDecimal multiplicand):乘法。
divide(BigDecimal divisor):除法。
remainder(BigDecimal divisor):返回除法的余数。
pow(int n):返回 BigDecimal 的 n 次幂。

5、比较和判断:
compareTo(BigDecimal val):比较两个 BigDecimal 的大小。
equals(Object obj):判断两个 BigDecimal 是否相等。
signum():返回 BigDecimal 的符号函数。

6、取整和舍入:

setScale(int newScale):设置小数点后的位数。
round(MathContext mc):使用指定的 MathContext 对 BigDecimal 进行舍入。

7、其他方法:
abs():返回 BigDecimal 的绝对值。
negate():返回 BigDecimal 的相反数。
intValue()、longValue()、doubleValue():将 BigDecimal 转换为对应的基本数据类型。

8、舍入方法

(1)、ROUND_UP(向上舍入):
如果数字是正数,则舍入行为同 Math.ceil。
如果数字是负数,则舍入行为同 Math.floor。
例子:1.5 舍入为 2.0,-1.5 舍入为 -1.0。

(2)、ROUND_DOWN(向下舍入):
向零的方向舍入,即截去多余的小数部分。
例子:1.5 舍入为 1.0,-1.5 舍入为 -1.0。

(3)、ROUND_CEILING(朝正无穷舍入):
向正无穷方向舍入,即始终向正数方向舍入。
例子:1.5 舍入为 2.0,-1.5 舍入为 -1.0。

(4)、ROUND_FLOOR(朝负无穷舍入):
向负无穷方向舍入,即始终向负数方向舍入。
例子:1.5 舍入为 1.0,-1.5 舍入为 -2.0。

(5)、ROUND_HALF_UP(四舍五入):
如果数字部分大于等于 0.5,则舍入行为同 Math.round。
例子:1.5 舍入为 2.0,-1.5 舍入为 -1.0。

(6)、ROUND_HALF_DOWN(五舍六入):
如果数字部分大于 0.5,则舍入行为同 Math.round。
例子:1.5 舍入为 1.0,-1.5 舍入为 -2.0。

(7)、ROUND_HALF_EVEN(银行家舍入法):
如果数字部分大于 0.5,则舍入行为同 Math.round。
如果数字部分等于 0.5,则舍入到最接近的偶数。
例子:1.5 舍入为 2.0,-1.5 舍入为 -2.0。

(8)、ROUND_UNNECESSARY(不需要舍入):
如果存在多余的小数部分,则抛出 ArithmeticException。

 

三、BigDecimalUtil工具类:对double类型的数据进行处理

如:加减乘除、四舍五入、类型转换、比较两个值的大小

工具类:

public class BigDecimalUtil {
    // 默认除法运算精度
    private static final int DEF_DIV_SCALE = 10;

    // 这个类不能实例化
    private BigDecimalUtil() {
    }

    /**
     * 提供精确的加法运算。
     *
     * @param v1
     *            被加数
     * @param v2
     *            加数
     * @return 两个参数的和
     */
    public static double add(double v1, double v2) {
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.add(b2).doubleValue();
    }

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

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

    /**
     * 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到 小数点以后10位,以后的数字四舍五入。
     *
     * @param v1
     *            被除数
     * @param v2
     *            除数
     * @return 两个参数的商
     */
    public static double div(double v1, double v2) {
        return div(v1, v2, DEF_DIV_SCALE);
    }

    /**
     * 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指 定精度,以后的数字四舍五入。
     *
     * @param v1
     *            被除数
     * @param v2
     *            除数
     * @param scale
     *            表示表示需要精确到小数点以后几位。
     * @return 两个参数的商
     */
    public static double div(double v1, double v2, int scale) {
        if (scale < 0) {
            throw new IllegalArgumentException(
                    "The scale must be a positive integer or zero");
        }
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
    }

    /**
     * 提供精确的小数位四舍五入处理。
     *
     * @param v
     *            需要四舍五入的数字
     * @param scale
     *            小数点后保留几位
     * @return 四舍五入后的结果
     */
    public static double round(double v, int scale) {
        if (scale < 0) {
            throw new IllegalArgumentException(
                    "The scale must be a positive integer or zero");
        }
        BigDecimal b = new BigDecimal(Double.toString(v));
        BigDecimal one = new BigDecimal("1");
        return b.divide(one, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
    }

    /**
     * 提供精确的类型转换(Float)
     *
     * @param v
     *            需要被转换的数字
     * @return 返回转换结果
     */
    public static float convertsToFloat(double v) {
        BigDecimal b = new BigDecimal(v);
        return b.floatValue();
    }

    /**
     * 提供精确的类型转换(Int)不进行四舍五入
     *
     * @param v
     *            需要被转换的数字
     * @return 返回转换结果
     */
    public static int convertsToInt(double v) {
        BigDecimal b = new BigDecimal(v);
        return b.intValue();
    }

    /**
     * 提供精确的类型转换(Long)
     *
     * @param v
     *            需要被转换的数字
     * @return 返回转换结果
     */
    public static long convertsToLong(double v) {
        BigDecimal b = new BigDecimal(v);
        return b.longValue();
    }

    /**
     * 返回两个数中大的一个值
     *
     * @param v1
     *            需要被对比的第一个数
     * @param v2
     *            需要被对比的第二个数
     * @return 返回两个数中大的一个值
     */
    public static double returnMax(double v1, double v2) {
        BigDecimal b1 = new BigDecimal(v1);
        BigDecimal b2 = new BigDecimal(v2);
        return b1.max(b2).doubleValue();
    }

    /**
     * 返回两个数中小的一个值
     *
     * @param v1
     *            需要被对比的第一个数
     * @param v2
     *            需要被对比的第二个数
     * @return 返回两个数中小的一个值
     */
    public static double returnMin(double v1, double v2) {
        BigDecimal b1 = new BigDecimal(v1);
        BigDecimal b2 = new BigDecimal(v2);
        return b1.min(b2).doubleValue();
    }

    /**
     * 精确对比两个数字
     *
     * @param v1
     *            需要被对比的第一个数
     * @param v2
     *            需要被对比的第二个数
     * @return 如果两个数一样则返回0,如果第一个数比第二个数大则返回1,反之返回-1
     */
    public static int compareTo(double v1, double v2) {
        BigDecimal b1 = new BigDecimal(v1);
        BigDecimal b2 = new BigDecimal(v2);
        return b1.compareTo(b2);
    }

    /**
     * 总数固定个数随机分配
     *
     * @param total
     *          总数
     * @param totalUser
     *          随机总个数
     * @return
     */
    public static BigDecimal[] randomAmount(BigDecimal total, int totalUser){
        BigDecimal perMax = total.divide(BigDecimal.valueOf(totalUser),4,BigDecimal.ROUND_HALF_DOWN).multiply(BigDecimal.valueOf(2));
        BigDecimal perMin = total.divide(BigDecimal.valueOf(totalUser),4,BigDecimal.ROUND_HALF_DOWN).divide(BigDecimal.valueOf(2),4, BigDecimal.ROUND_HALF_DOWN);
        int i = 0; //第几人
        BigDecimal[] array = new BigDecimal[totalUser];//分配结果集
        Random ran = new Random();
        BigDecimal yet = BigDecimal.ZERO; // 已分配的总金额

        //保证每个人有最小金额+随机值
        for (i = 0; i < totalUser; i++) {
            array[i] = perMin.add(BigDecimal.valueOf(ran.nextDouble()).multiply(BigDecimal.valueOf
                    (total.doubleValue()/totalUser - perMin.doubleValue())).setScale(4,BigDecimal.ROUND_HALF_DOWN));
            yet = yet.add(array[i]);
        }

        while (yet.compareTo(total) < 0){
            BigDecimal thisM =  BigDecimal.valueOf(ran.nextDouble()).multiply(BigDecimal.valueOf(perMax.doubleValue() -
                    perMin.doubleValue())).setScale(4,BigDecimal.ROUND_HALF_DOWN);
            i = ran.nextInt(totalUser); //随机选择人
            if (yet.doubleValue() + thisM.doubleValue() > total.doubleValue()){
                thisM = total.subtract(yet);
            }

            if (array[i].doubleValue() + thisM.doubleValue() < perMax.doubleValue())//判断是否超出最大金额
            {
                array[i] = array[i].add(thisM);
                yet = yet.add(thisM);
            }
        }

//        Array.Sort(array);
//        yet = BigDecimal.ZERO;
//        for (i = 0; i < totalUser; i++)
//        {
//            yet = yet.add(array[i]);
//            System.out.println("第{"+(i+1)+"}人=>分配{"+array[i]+"}元,合计分配{"+yet+"}元\r\n");
//        }
        return array;
    }
}

使用工具类如下:

public class demo4 {
    public static void main(String[] args) {
        double number1 = 1;
        double number2 = 20.2;
        double number3 = 300.03;
//        double result = new BigDecimal(Double.toString(number1))
//                .add(new BigDecimal(Double.toString(number2)))
//                .add(new BigDecimal(Double.toString(number3))).doubleValue();
        double res1 = BigDecimalUtil.add(number1, number2);
        double result = BigDecimalUtil.add(res1, number3);
//        double result = number1 + number2 + number3;
        System.out.println(result);
    }
}

结果:321.23

项目中使用该工具类的相关代码:

// 如果Output表中已经存在该节点(插入数据的节点)的信息,则修改Output表中的这条信息,其中完工数量就是获取的数据的完工数量加上已经存在的节点的完工数量,另外还要修改Input表中的数据
output.setId(outputList1.get(0).getId());
Double completeDty1 = Double.valueOf(StringUtils.isEmpty(productFeedERPDetailInfo.getCompleteQty()) ? "0" : productFeedERPDetailInfo.getCompleteQty());
Double completeDty2 = Double.valueOf(StringUtils.isEmpty(outputList1.get(0).getCompleteQty()) ? "0" : outputList1.get(0).getCompleteQty());
output.setCompleteQty(String.valueOf(BigDecimalUtil.add(completeDty1, completeDty2)));// 完工数量

 

posted on 2021-01-21 09:54  周文豪  阅读(572)  评论(0)    收藏  举报