一文详解BigDecimal精度丢失处理方案

1,611 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第24天,点击查看活动详情

在涉及到金额处理场景时,常常会使用到BigDecimal类型来进行处理。

Java在java.math包中提供的API类BigDecimal,用来对超过16位有效位的数进行精确的运算。双精度浮点型变量double可以处理16位有效数。在实际应用中,需要对更大或者更小的数进行运算和处理。float和double只能用来做科学计算或者是工程计算,在商业计算中要用java.math.BigDecimal。BigDecimal所创建的是对象,我们不能使用传统的+、-、*、/等算术运算符直接对其对象进行数学运算,而必须调用其相对应的方法。方法中的参数也必须是BigDecimal的对象。构造器是类的特殊方法,专门用来创建对象,特别是带有参数的对象。

问题

BigDecimal 有参构造方法,传入 double,float 会丢失精度。

BigDecimal bigDecimal=new BigDecimal(11);
System.out.println(bigDecimal);
bigDecimal=new BigDecimal("1.1");
System.out.println(bigDecimal);
bigDecimal=new BigDecimal(1.1d);
System.out.println(bigDecimal);
bigDecimal=new BigDecimal(1.1f);
System.out.println(bigDecimal);

运行上面代码,期待结果应该都是11、1.1、1.1、1.1。 但实际结果,如下所示:

11
1.1
1.100000000000000088817841970012523233890533447265625
1.10000002384185791015625

原因

事实上,Double等浮点运算的数据本身就是不精确的数据而是近似值,因此才会出现精度丢失的问题。

Double计算不精确的原因

因为在运算时,是先转换成机器数在运算,也就是二进制数据。但在转换成二进制数的时候,存储小数部分的位数会出现不够的情况,即无限循环小数,所以就造成了一下误差。解决方法也是,在先toString后再使用BigDecimal进行转换一下即可。

阿里巴巴Java开发手册说明

《阿里巴巴Java开发手册-泰山版》中有针对这部分,进行了说明,并明确禁止直接将Double等浮点数转换为BigDecimal类型的数据:

image-20220830214227441

处理方案

精度丢失处理方案

String、int 不会丢失精度,将double,float转成String再计算可以避免精度丢失问题

package com.example.demo.utils;
​
import java.math.BigDecimal;
​
public class BigDecimalUtils {
​
    public static BigDecimal doubleAdd(double v1, double v2) {
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.add(b2);
    }
​
    public static BigDecimal floatAdd(float v1, float v2) {
        BigDecimal b1 = new BigDecimal(Float.toString(v1));
        BigDecimal b2 = new BigDecimal(Float.toString(v2));
        return b1.add(b2);
    }
​
    public static BigDecimal doubleSubtract(double v1, double v2) {
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.subtract(b2);
    }
​
    public static BigDecimal floatSubtract(float v1, float v2) {
        BigDecimal b1 = new BigDecimal(Float.toString(v1));
        BigDecimal b2 = new BigDecimal(Float.toString(v2));
        return b1.subtract(b2);
    }
​
    public static BigDecimal doubleMultiply(double v1, double v2) {
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.multiply(b2);
    }
​
    public static BigDecimal floatMultiply(float v1, float v2) {
        BigDecimal b1 = new BigDecimal(Float.toString(v1));
        BigDecimal b2 = new BigDecimal(Float.toString(v2));
        return b1.multiply(b2);
    }
​
    public static BigDecimal doubleDivide(double v1, double v2) {
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
​
        return b1.divide(b2, 2, BigDecimal.ROUND_HALF_UP);
    }
​
    public static BigDecimal floatDivide(float v1, float v2) {
        BigDecimal b1 = new BigDecimal(Float.toString(v1));
        BigDecimal b2 = new BigDecimal(Float.toString(v2));
​
        return b1.divide(b2, 2, BigDecimal.ROUND_HALF_UP);
    }
​
    public static int doubleCompareTo(double v1, double v2) {
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return  b1.compareTo(b2);
    }
​
    public static int floatCompareTo(float v1, float v2) {
        BigDecimal b1 = new BigDecimal(Float.toString(v1));
        BigDecimal b2 = new BigDecimal(Float.toString(v2));
        return  b1.compareTo(b2);
    }
​
}

测试

System.out.println("-----------------Add------------------");
System.out.println(BigDecimalUtils.doubleAdd(1.1, 2.2));
System.out.println(BigDecimalUtils.floatAdd(1.1f, 2.2f));
System.out.println("-----------------Subtract------------------");
System.out.println(BigDecimalUtils.doubleSubtract(2.2, 1.1));
System.out.println(BigDecimalUtils.floatSubtract(2.2f, 1.1f));
System.out.println("-----------------Multiply------------------");
System.out.println(BigDecimalUtils.doubleMultiply(2.2, 1.1));
System.out.println(BigDecimalUtils.floatMultiply(2.2f, 1.1f));
System.out.println("-----------------Divide------------------");
System.out.println(BigDecimalUtils.doubleDivide(2.2, 1.1));
System.out.println(BigDecimalUtils.floatDivide(2.2f, 1.1f));

结果打印:

-----------------Add------------------
3.3
3.3
-----------------Subtract------------------
1.1
1.1
-----------------Multiply------------------
2.42
2.42
-----------------Divide------------------
2.00
2.00

欢迎关注白羊,感谢观看ヾ(◍°∇°◍)ノ゙