正確な浮動小数点演算ツールクラスおよび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
"
, b1.toString() ); System.out.printf("new BigDecimal(String d) :%s
"
, b2.toString() ); System.out.printf("new BigDecimal(double d) :%s
"
, 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
"
, b1.toString() ); System.out.printf("new BigDecimal(String d) :%s
"
, b2.toString() ); System.out.printf("new BigDecimal(double d) :%s
"
, 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を正確に10進法の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)を使用しないでください.