工具类如下:
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)。