精确浮点运算工具类以及BigDecimal.valueOf(double d)与new BigDecimal(double d)的区别

工具类如下:

package com.iscas.common.tools.core.arithmetic;

import java.math.BigDecimal;
import java.math.RoundingMode;

/**
 * 浮点数精确运算工具类
 *
 * 使用BigDecimal代替直接浮点运算,在大量数据计算时效率低,谨慎使用
 *
 * @author zhuquanwen
 * @vesion 1.0
 * @date 2020/8/11 21:00
 * @since jdk1.8
 */
public class FloatExactArithUtils {
    private FloatExactArithUtils() {}

    /**
     * 精确的浮点加法运算
     * */
    public static double add(double data1, double data2) {
        BigDecimal b1 = BigDecimal.valueOf(data1);
        BigDecimal b2 = BigDecimal.valueOf(data2);
        return b1.add(b2).doubleValue();
    }

    /**
     * 精确的浮点减法运算
     * */
    public static double subtract(double data1, double data2) {
        BigDecimal b1 = BigDecimal.valueOf(data1);
        BigDecimal b2 = BigDecimal.valueOf(data2);
        return b1.subtract(b2).doubleValue();
    }

    /**
     * 精确的浮点乘法运算
     * */
    public static double multiply(double data1, double data2) {
        BigDecimal b1 = BigDecimal.valueOf(data1);
        BigDecimal b2 = BigDecimal.valueOf(data2);
        return b1.multiply(b2).doubleValue();
    }

    /**
     * 相对精确的除法运算,指定精度后的数字四舍五入
     * */
    public static double divide(double data1, double data2, int scale) {
        if (scale < 0) {
            throw new RuntimeException("精度不能小于0");
        }
        BigDecimal b1 = BigDecimal.valueOf(data1);
        BigDecimal b2 = BigDecimal.valueOf(data2);
        return b1.divide(b2, scale, RoundingMode.HALF_UP).doubleValue();
    }

    /**
     * 相对精确的除法运算,指定精度后的数字四舍五入,使用默认精度10
     * */
    public static double divide(double data1, double data2) {
       return divide(data1, data2, 10);
    }

}

测试:

package com.iscas.common.tools.arithmetic;

import com.iscas.common.tools.core.arithmetic.FloatExactArithUtils;
import lombok.extern.slf4j.Slf4j;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

import java.math.BigDecimal;

/**
 * 浮点精确计算工具类测试
 *
 * @author zhuquanwen
 * @vesion 1.0
 * @date 2020/8/11 21:05
 * @since jdk1.8
 */
@RunWith(JUnit4.class)
@Slf4j
public class FloatExactArithUtilsTests {

    @Test
    public void testAdd() {
        double data1 = 0.01;
        double data2 = 0.05;
        log.debug(String.format("不精确计算的结果:%f", (data1 + data2)));
        double result = FloatExactArithUtils.add(data1, data2);
        log.debug(String.format("精确计算的结果:%f", result));
        Assert.assertEquals(0.06, result, 0);
    }

    @Test
    public void testSub() {
        double data1 = 0.051;
        double data2 = 0.01;
        log.debug(String.format("不精确计算的结果:%f", (data1 - data2)));
        double result = FloatExactArithUtils.subtract(data1, data2);
        log.debug(String.format("精确计算的结果:%f", result));
        Assert.assertEquals(0.041, result, 0);
    }

    @Test
    public void testMultiply() {
        double data1 = 0.051;
        double data2 = 0.01;
        log.debug(String.format("不精确计算的结果:%f", (data1 * data2)));
        double result = FloatExactArithUtils.multiply(data1, data2);
        log.debug(String.format("精确计算的结果:%f", result));
        Assert.assertEquals(0.00051, result, 0);
    }

    @Test
    public void testDivide() {
        double data1 = 0.051;
        double data2 = 0.01;
        log.debug(String.format("不精确计算的结果:%f", (data1 / data2)));
        double result = FloatExactArithUtils.divide(data1, data2, 1);
        log.debug(String.format("精确计算的结果:%f", result));
        Assert.assertEquals(5.1, result, 0);
    }

    @Test
    public void testBigDecimal() {
        double d = 0.999;
        BigDecimal b1 = BigDecimal.valueOf(d);
        BigDecimal b2 = new BigDecimal("0.999");
        BigDecimal b3 = new BigDecimal(d);
        System.out.printf("BigDecimal.valueOf(double d)的结果:%s\n", b1.toString() );
        System.out.printf("new BigDecimal(String d)的结果:%s\n", b2.toString() );
        System.out.printf("new BigDecimal(double d)的结果:%s\n", b3.toString() );
    }
}

BigDecimal.valueOf与new BigDecimal的区别
测试如下:

 @Test
    public void testBigDecimal() {
        double d = 0.999;
        BigDecimal b1 = BigDecimal.valueOf(d);
        BigDecimal b2 = new BigDecimal("0.999");
        BigDecimal b3 = new BigDecimal(d);
        System.out.printf("BigDecimal.valueOf(double d)的结果:%s\n", b1.toString() );
        System.out.printf("new BigDecimal(String d)的结果:%s\n", b2.toString() );
        System.out.printf("new BigDecimal(double d)的结果:%s\n", b3.toString() );
    }
}

运行后
测试结果如下:


BigDecimal.valueOf(double d)的结果:0.999
new BigDecimal(String d)的结果:0.999
new BigDecimal(double d)的结果:0.99899999999999999911182158029987476766109466552734375



Process finished with exit code 0

通过测试结果推算得知:
new BigDecimal(double d) 将浮点数据d精确的转换成十进制的BigDecimal,因为0.999不能精确的被double表示,所以造成了精度丢失的现象。而BigDecimal.valueOf(double d) 是先将d转为了字符串,源码如下:

  public static BigDecimal valueOf(double val) {
        // Reminder: a zero double returns '0.0', so we cannot fastpath
        // to use the constant ZERO.  This might be important enough to
        // justify a factory approach, a cache, or a few private
        // constants, later.
        return new BigDecimal(Double.toString(val));
    }

所以要精确计算的时候应该使用BigDecimal.valueOf(double d)或new BigDecimal(String d),不要使用
new BigDecimal(double d)。

你可能感兴趣的:(java,BigDecimal,浮点运算,精确浮点运算,浮点运算工具类,不失精度的运算)