科普文:浮点数精度运算BigDecimal踩坑和填坑

概叙

用过Java的BigDecimal类型,但是很多人都用错了。如果使用不当,可能会造成非常致命的线上问题,因为这涉及到金额等数据的计算精度。

首先说一下,一般对于不需要特别高精度的计算,我们使用double或float类型就可以了。

由于计算机天生的无法表达完整的二进制浮点数的小数,二进制的小数是无限循环的,所以只能无限接近于精确值,这就造成了浮点计算的精度问题。此时就需要使用BigDecimal类型了。

java 复制代码
 * 2. BigDecimal 类的使用
 * 2.1 创建 BigDecimal 对象
 * 要创建一个BigDecimal对象,我们可以使用以下几种方式:
 *
 * 2.1.1 通过字符串创建
 * BigDecimal bd1 = new BigDecimal("123.45");
 * BigDecimal bd2 = new BigDecimal("67.89");
 * 2.1.2 通过整数值创建
 * BigDecimal bd3 = BigDecimal.valueOf(100);
 * BigDecimal bd4 = BigDecimal.valueOf(200);
 * 2.1.3 通过浮点数值创建
 * BigDecimal bd5 = BigDecimal.valueOf(3.14);
 * BigDecimal bd6 = BigDecimal.valueOf(2.71);
 * 2.2 常用方法
 * BigDecimal类提供了许多常用的方法,用于进行精确的数学运算。下面是一些常用的方法:
 *
 * 2.2.1 加法
 * BigDecimal sum = bd1.add(bd2);
 * 2.2.2 减法
 * BigDecimal diff = bd1.subtract(bd2);
 * 2.2.3 乘法
 * BigDecimal product = bd1.multiply(bd2);
 * 2.2.4 除法
 * BigDecimal quotient = bd1.divide(bd2, RoundingMode.HALF_UP);
 * 2.2.5 取余
 * BigDecimal remainder = bd1.remainder(bd2);
 * 2.2.6 比较大小
 * int result = bd1.compareTo(bd2);
 * 2.2.7 取绝对值
 * BigDecimal absoluteValue = bd1.abs();
 * 2.2.8 取最大值
 * BigDecimal max = bd1.max(bd2);
 * 2.2.9 取最小值
 * BigDecimal min = bd1.min(bd2);
 * 2.3 精度设置
 * 在进行浮点数计算时,我们经常需要设置精度,以控制小数点后的位数。BigDecimal类提供了setScale方法,用于设置精度。
 * BigDecimal result = bd1.divide(bd2, 4, RoundingMode.HALF_UP);
 * 上面的代码将结果保留四位小数,并且使用了HALF_UP舍入模式。
 *
 * 2.4 注意事项
 * 在使用BigDecimal进行浮点数计算时,需要注意以下几点:
 *
 * 避免使用浮点数进行计算,而是使用字符串、整数或BigDecimal对象。
 * 使用适当的舍入模式来处理精度问题。
 * 不要使用equals方法进行BigDecimal对象的相等比较,而应该使用compareTo方法。

踩坑:浮点运算的精度坑

1 - 0.8f = 0.19999999

java 复制代码
        float a=1;
        float b=0.8f;
        //a -b = 0.19999999
        // todo 打印结果会是0.2吗?不是,打印结果是0.19999999。
        //  因为b最大化接近于0.8,可能是0.80000001,近似于0.8。
        //  这就是为什么说精度要求不高时可以用double或float类型,
        //  一旦涉及到金额就不能使用浮点类型的原因。

        // a=1.0  b=0.8
        ZhouxxTool.printTimeAndThread(ZhouxxTool.getTimeAndThread("a=" + a +"  b=" + b));
        ZhouxxTool.printTimeAndThread(ZhouxxTool.getTimeAndThread("a -b = " + (a-b)));
        //2024-07-10 18:50:55 416	|	1720608655416	|	1	|	main	|	2024-07-10 18:45:55 413	|	1720608655416	|	1	|	main	|	a -b = 0.19999999

踩坑:空格等字符串转BigDecimal类型抛异常 NumberFormatException

java 复制代码
        //
        BigDecimal a1 =new BigDecimal("00");
        //BigDecimal a2 =new BigDecimal(" 00 ");// todo Exception in thread "main" java.lang.NumberFormatException
        BigDecimal a22 =BigDecimalUtil.strToDecimalDefaultNull(" 00 "); // todo 工具转换
        //BigDecimal a2 =new BigDecimal("0 0");// todo  Exception in thread "main" java.lang.NumberFormatException
        BigDecimal a222 =BigDecimalUtil.strToDecimalDefaultNull("0 0");// todo 工具转换
        BigDecimal a2 =new BigDecimal("0.0");
        BigDecimal b1 = null ;
        //BigDecimal c1 =new BigDecimal("") ;// todo Exception in thread "main" java.lang.NumberFormatException
        BigDecimal c11 =BigDecimalUtil.strToDecimalDefaultNull("");// todo 工具转换
        BigDecimal c1 =new BigDecimal("0") ;
        //BigDecimal c2 =new BigDecimal(" ") ;// todo Exception in thread "main" java.lang.NumberFormatException
        BigDecimal c22 =BigDecimalUtil.strToDecimalDefaultNull(" ");// todo 工具转换
        BigDecimal c2 =new BigDecimal("0") ;
        //BigDecimal c3 =new BigDecimal("  ") ; // todo Exception in thread "main" java.lang.NumberFormatException
        BigDecimal c3 =new BigDecimal("01") ;

        // a22=null  a222=null //todo 工具类全部将其置为 null
        ZhouxxTool.printTimeAndThread(ZhouxxTool.getTimeAndThread("a22=" + a22 +"  a222=" + a222));
        // c11=null  c22=null  //todo 工具类全部将其置为 null
        ZhouxxTool.printTimeAndThread(ZhouxxTool.getTimeAndThread("c11=" + c11 +"  c22=" + c22));

        //a1=0  a2=0.0
        ZhouxxTool.printTimeAndThread(ZhouxxTool.getTimeAndThread("a1=" + a1 +"  a2=" + a2));
        //b1=null   c1=0
        ZhouxxTool.printTimeAndThread(ZhouxxTool.getTimeAndThread("b1=" + b1+"   c1=" + c1));
        //c2=0  c3=1
        ZhouxxTool.printTimeAndThread(ZhouxxTool.getTimeAndThread("c2=" + c2 +"  c3=" + c3));

踩坑:使用BigDecimal构造函数和使用valueOf方法初始化,两种方式得到的结果不一样

java 复制代码
     public static void  test1() {
         BigDecimal a = BigDecimal.ONE;
         // todo 踩坑: 使用BigDecimal构造函数和使用valueOf方法初始化,两种方式得到的结果不一样,
         BigDecimal b = new BigDecimal(0.8);
         // valueOf方法初始化的BigDecimal数据计算是精确的。
         BigDecimal c = BigDecimal.valueOf(0.8);
         // todo 使用BigDecimal构造函数时,传字符串而不要传浮点类型。尽量使用valueOf方法初始化,并且不要传float类型数据。

         // a=1  b=0.8000000000000000444089209850062616169452667236328125  c=0.8
         ZhouxxTool.printTimeAndThread(ZhouxxTool.getTimeAndThread("a=" + a +"  b=" + b+"  c=" + c));

         // a-b=0.1999999999999999555910790149937383830547332763671875
         ZhouxxTool.printTimeAndThread(ZhouxxTool.getTimeAndThread("a-b=" + (a.subtract(b))));
         // a-c=0.2
         ZhouxxTool.printTimeAndThread(ZhouxxTool.getTimeAndThread("a-c=" + (a.subtract(c))));
     }

踩坑:equals判断a和b不相等(含精度比较),compareTo判断a和b相等(大小比较,不含精度)

java 复制代码
    public static void  test2(){
         BigDecimal a=new BigDecimal("0.02");
         BigDecimal b=new BigDecimal( "0.020");

         // a=0.02  b=0.020  todo 精度不一样
        ZhouxxTool.printTimeAndThread(ZhouxxTool.getTimeAndThread("a=" + a +"  b=" + b));

        // todo 踩坑: equals判断a和b不相等,compareTo判断a和b相等
         // a.equals(b):false  // todo equals除了比较值的大小,还会比较值的精度。 todo 比较大小且限制精度,使用equals方法。
        ZhouxxTool.printTimeAndThread(ZhouxxTool.getTimeAndThread("a.equals(b):"+ a.equals(b)));
        // a.compareTo(b):0 // todo 比较两个BigDecimal值的大小,使用compareTo方法。
        ZhouxxTool.printTimeAndThread(ZhouxxTool.getTimeAndThread("a.compareTo(b):"+(a.compareTo(b))));

    }

等值比较

踩坑:使用BigDecimal进行计算时,结果一定要设置精度和舍入模式。

java 复制代码
    public static void  test3(){
        BigDecimal a=new BigDecimal("1.0");
        BigDecimal b=new BigDecimal("3.0");
        // todo 在进行BigDecimal计算特别是除法计算时,很多人会忘记设置精度和舍入模式,最终结果就是程序会报一个ArithmeticException异常。
        //a.divide(b); //意思就是如果divide方法计算得到的商是一个无限小数,而代码预期得到一个精确数字,那么就会抛出ArithmeticException异常。
        /**
         *  todo  踩坑:使用BigDecimal进行计算时,结果一定要设置精度和舍入模式。
         * 如果商具有非终止的十进制展开,并且指定运算返回精确的结果,则引发ArithmeticException。
         * 否则,将返回除法的确切结果,就像对其他操作所做的那样。
         * Exception in thread "main" java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
         * 	at java.math.BigDecimal.divide(BigDecimal.java:1690)
         * 	at com.zxx.study.base.scale.BigDecimalError.test3(BigDecimalError.java:47)
         * 	at com.zxx.study.base.scale.BigDecimalError.main(BigDecimalError.java:23)
         * */

        // a=1.0  b=3.0
        ZhouxxTool.printTimeAndThread(ZhouxxTool.getTimeAndThread("a=" + a +"  b=" + b));

        // 设置了c的精度为2,RoundingMode.HALF_UP是向上舍入模式,即四舍五入。
        BigDecimal c=a.divide(b,2, RoundingMode.HALF_UP);
        // a/b=0.33
        ZhouxxTool.printTimeAndThread(ZhouxxTool.getTimeAndThread("a/b="+ c));
    }

踩坑:toString方法将BigDecimal的值转成了科学计数法的值

java 复制代码
    public static void  test4(){
        BigDecimal   a= BigDecimal.valueOf(567584568686785678678.6786786785978987090456);
        // 567584568686785678678.6786786785978987090456 =5.6758456868678566E+20
        // todo 踩坑: toString方法将BigDecimal的值转成了科学计数法的值。
        ZhouxxTool.printTimeAndThread(ZhouxxTool.getTimeAndThread(" 567584568686785678678.6786786785978987090456 ="+ a));
        /**
         * 我们可以先看下BigDecimal三种转字符串的方法。
         * toString():如果需要指数,则使用科学计数法。
         * toPlainString():不带指数的字符串表现形式。
         * toEngineeringString():如果需要指数,则使用工程计数法。
         * todo 利用NumberFormat类,对BigDecimal进行格式化控制。
         * */
    }

利用NumberFormat类,对BigDecimal进行格式化控制。

java 复制代码
    public static void  test5(){
        // todo 利用NumberFormat类,对BigDecimal进行格式化控制。
        // NumberFormat.getCurrencyInstance()方法用于获取一个货币格式化的实例,该实例可以根据默认或指定的地区设置将数字格式化为货币值。
        NumberFormat currency=NumberFormat.getCurrencyInstance();
        NumberFormat percent = NumberFormat.getPercentInstance();
        percent.setMaximumFractionDigits(3);//百分比小数点最多3位
        BigDecimal loanAmount =new BigDecimal("60000.88");//金额
        BigDecimal interestRate=new BigDecimal( "0.008");//利率
        BigDecimal interest=loanAmount.multiply(interestRate);//相乘

        // loanAmount=60000.88  interestRate=0.008  interest=480.00704
        ZhouxxTool.printTimeAndThread(ZhouxxTool.getTimeAndThread("loanAmount=" + loanAmount +"  interestRate=" + interestRate+"  interest=" + interest));

        // 创建一个法国的货币格式化实例
        //        NumberFormat n = NumberFormat.getCurrencyInstance(Locale.FRANCE);
        //金硕:	¥60,000.88  todo 当前环境变量是中国  所以用人民币格式展示
        ZhouxxTool.printTimeAndThread(ZhouxxTool.getTimeAndThread("金硕:\t"+ currency.format(loanAmount)));
        // 利率:	0.8%
        ZhouxxTool.printTimeAndThread(ZhouxxTool.getTimeAndThread("利率:\t"+ percent.format(interestRate)));
        // 利息:	¥480.01  todo 当前环境变量是中国  所以用人民币格式展示  todo这里还默认四舍五入了
        ZhouxxTool.printTimeAndThread(ZhouxxTool.getTimeAndThread("利息:\t"+currency.format(interest)));
    }

BigDecimalUtil工具类

1.使用工具类中String方法进行运算

2.运算时,指定精度和舍入模式

3.默认值和默认精度设置

java 复制代码
    /**
     * 字符串转BigDecimal 为空则返回Zero
     */
    public static BigDecimal strToDecimalDefaultZero(String s) {
        // todo 数据为空则返回Zero
        return strToDecimal(s, BigDecimal.ZERO);
    }

    /**
     * 字符串转BigDecimal 为空则返回空
     */
    public static BigDecimal strToDecimalDefaultNull(String s) {
        return strToDecimal(s, null);
    }

    /**
     * 字符串转BigDecimal 为空默认值defaultV
     */
    private static BigDecimal strToDecimal(String s, BigDecimal defaultV) {
        if (ObjectUtils.isEmpty(s)) {
            // todo 默认值为 null
            return defaultV;
        }
        try {
            // todo 默认精度4位小数  且四舍五入
            return new BigDecimal(s).setScale(scaleDefault, roundingModeDefault);
        } catch (Exception e) {
            /**
             * todo new BigDecimal(s).setScale 会抛出两种异常
             *
             *舍入模式 =7 ROUND_UNNECESSARY 会报算数异常 ArithmeticException -- if roundingMode==ROUND_UNNECESSARY and the specified scaling operation would require rounding.
             *s舍入模式 不在[0,7] 会报无效参数异常 IllegalArgumentException -- if roundingMode does not represent a valid rounding mode.
             * */
            // todo 精度设置异常  则返回null
            return defaultV;
        }
    }

完整代码

踩坑代码

java 复制代码
package com.zxx.study.base.scale;

import com.zxx.study.base.util.ZhouxxTool;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.NumberFormat;

/**
 * @author zhouxx
 * @create 2024-07-10 18:43
 */
public class BigDecimalError {

    public static void main(String[] args) {
        float a=1;
        float b=0.8f;
        //a -b = 0.19999999
        // todo 打印结果会是0.2吗?不是,打印结果是0.19999999。
        //  因为b最大化接近于0.8,可能是0.80000001,近似于0.8。
        //  这就是为什么说精度要求不高时可以用double或float类型,
        //  一旦涉及到金额就不能使用浮点类型的原因。

        // a=1.0  b=0.8
        ZhouxxTool.printTimeAndThread(ZhouxxTool.getTimeAndThread("a=" + a +"  b=" + b));
        ZhouxxTool.printTimeAndThread(ZhouxxTool.getTimeAndThread("a -b = " + (a-b)));
        //2024-07-10 18:50:55 416	|	1720608655416	|	1	|	main	|	2024-07-10 18:45:55 413	|	1720608655416	|	1	|	main	|	a -b = 0.19999999

        //
        BigDecimal a1 =new BigDecimal("00");
        //BigDecimal a2 =new BigDecimal(" 00 ");// todo Exception in thread "main" java.lang.NumberFormatException
        BigDecimal a22 =BigDecimalUtil.strToDecimalDefaultNull(" 00 "); // todo 工具转换
        //BigDecimal a2 =new BigDecimal("0 0");// todo  Exception in thread "main" java.lang.NumberFormatException
        BigDecimal a222 =BigDecimalUtil.strToDecimalDefaultNull("0 0");// todo 工具转换
        BigDecimal a2 =new BigDecimal("0.0");
        BigDecimal b1 = null ;
        //BigDecimal c1 =new BigDecimal("") ;// todo Exception in thread "main" java.lang.NumberFormatException
        BigDecimal c11 =BigDecimalUtil.strToDecimalDefaultNull("");// todo 工具转换
        BigDecimal c1 =new BigDecimal("0") ;
        //BigDecimal c2 =new BigDecimal(" ") ;// todo Exception in thread "main" java.lang.NumberFormatException
        BigDecimal c22 =BigDecimalUtil.strToDecimalDefaultNull(" ");// todo 工具转换
        BigDecimal c2 =new BigDecimal("0") ;
        //BigDecimal c3 =new BigDecimal("  ") ; // todo Exception in thread "main" java.lang.NumberFormatException
        BigDecimal c3 =new BigDecimal("01") ;

        // a22=null  a222=null //todo 工具类全部将其置为 null
        ZhouxxTool.printTimeAndThread(ZhouxxTool.getTimeAndThread("a22=" + a22 +"  a222=" + a222));
        // c11=null  c22=null  //todo 工具类全部将其置为 null
        ZhouxxTool.printTimeAndThread(ZhouxxTool.getTimeAndThread("c11=" + c11 +"  c22=" + c22));

        //a1=0  a2=0.0
        ZhouxxTool.printTimeAndThread(ZhouxxTool.getTimeAndThread("a1=" + a1 +"  a2=" + a2));
        //b1=null   c1=0
        ZhouxxTool.printTimeAndThread(ZhouxxTool.getTimeAndThread("b1=" + b1+"   c1=" + c1));
        //c2=0  c3=1
        ZhouxxTool.printTimeAndThread(ZhouxxTool.getTimeAndThread("c2=" + c2 +"  c3=" + c3));

        test1();
        test2();
        test3();
        test4();
        test5();

    }

     public static void  test1() {
         BigDecimal a = BigDecimal.ONE;
         // todo 踩坑: 使用BigDecimal构造函数和使用valueOf方法初始化,两种方式得到的结果不一样,
         BigDecimal b = new BigDecimal(0.8);
         // valueOf方法初始化的BigDecimal数据计算是精确的。
         BigDecimal c = BigDecimal.valueOf(0.8);
         // todo 使用BigDecimal构造函数时,传字符串而不要传浮点类型。尽量使用valueOf方法初始化,并且不要传float类型数据。

         // a=1  b=0.8000000000000000444089209850062616169452667236328125  c=0.8
         ZhouxxTool.printTimeAndThread(ZhouxxTool.getTimeAndThread("a=" + a +"  b=" + b+"  c=" + c));

         // a-b=0.1999999999999999555910790149937383830547332763671875
         ZhouxxTool.printTimeAndThread(ZhouxxTool.getTimeAndThread("a-b=" + (a.subtract(b))));
         // a-c=0.2
         ZhouxxTool.printTimeAndThread(ZhouxxTool.getTimeAndThread("a-c=" + (a.subtract(c))));
     }
    public static void  test2(){
         BigDecimal a=new BigDecimal("0.02");
         BigDecimal b=new BigDecimal( "0.020");

         // a=0.02  b=0.020  todo 精度不一样
        ZhouxxTool.printTimeAndThread(ZhouxxTool.getTimeAndThread("a=" + a +"  b=" + b));

        // todo 踩坑: equals判断a和b不相等,compareTo判断a和b相等
         // a.equals(b):false  // todo equals除了比较值的大小,还会比较值的精度。 todo 比较大小且限制精度,使用equals方法。
        ZhouxxTool.printTimeAndThread(ZhouxxTool.getTimeAndThread("a.equals(b):"+ a.equals(b)));
        // a.compareTo(b):0 // todo 比较两个BigDecimal值的大小,使用compareTo方法。
        ZhouxxTool.printTimeAndThread(ZhouxxTool.getTimeAndThread("a.compareTo(b):"+(a.compareTo(b))));

    }

    public static void  test3(){
        BigDecimal a=new BigDecimal("1.0");
        BigDecimal b=new BigDecimal("3.0");
        // todo 在进行BigDecimal计算特别是除法计算时,很多人会忘记设置精度和舍入模式,最终结果就是程序会报一个ArithmeticException异常。
        //a.divide(b); //意思就是如果divide方法计算得到的商是一个无限小数,而代码预期得到一个精确数字,那么就会抛出ArithmeticException异常。
        /**
         *  todo  踩坑:使用BigDecimal进行计算时,结果一定要设置精度和舍入模式。
         * 如果商具有非终止的十进制展开,并且指定运算返回精确的结果,则引发ArithmeticException。
         * 否则,将返回除法的确切结果,就像对其他操作所做的那样。
         * Exception in thread "main" java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
         * 	at java.math.BigDecimal.divide(BigDecimal.java:1690)
         * 	at com.zxx.study.base.scale.BigDecimalError.test3(BigDecimalError.java:47)
         * 	at com.zxx.study.base.scale.BigDecimalError.main(BigDecimalError.java:23)
         * */

        // a=1.0  b=3.0
        ZhouxxTool.printTimeAndThread(ZhouxxTool.getTimeAndThread("a=" + a +"  b=" + b));

        // 设置了c的精度为2,RoundingMode.HALF_UP是向上舍入模式,即四舍五入。
        BigDecimal c=a.divide(b,2, RoundingMode.HALF_UP);
        // a/b=0.33
        ZhouxxTool.printTimeAndThread(ZhouxxTool.getTimeAndThread("a/b="+ c));
    }

    public static void  test4(){
        BigDecimal   a= BigDecimal.valueOf(567584568686785678678.6786786785978987090456);
        // 567584568686785678678.6786786785978987090456 =5.6758456868678566E+20
        // todo 踩坑: toString方法将BigDecimal的值转成了科学计数法的值。
        ZhouxxTool.printTimeAndThread(ZhouxxTool.getTimeAndThread(" 567584568686785678678.6786786785978987090456 ="+ a));
        /**
         * 我们可以先看下BigDecimal三种转字符串的方法。
         * toString():如果需要指数,则使用科学计数法。
         * toPlainString():不带指数的字符串表现形式。
         * toEngineeringString():如果需要指数,则使用工程计数法。
         * todo 利用NumberFormat类,对BigDecimal进行格式化控制。
         * */
    }
    public static void  test5(){
        // todo 利用NumberFormat类,对BigDecimal进行格式化控制。
        // NumberFormat.getCurrencyInstance()方法用于获取一个货币格式化的实例,该实例可以根据默认或指定的地区设置将数字格式化为货币值。
        NumberFormat currency=NumberFormat.getCurrencyInstance();
        NumberFormat percent = NumberFormat.getPercentInstance();
        percent.setMaximumFractionDigits(3);//百分比小数点最多3位
        BigDecimal loanAmount =new BigDecimal("60000.88");//金额
        BigDecimal interestRate=new BigDecimal( "0.008");//利率
        BigDecimal interest=loanAmount.multiply(interestRate);//相乘

        // loanAmount=60000.88  interestRate=0.008  interest=480.00704
        ZhouxxTool.printTimeAndThread(ZhouxxTool.getTimeAndThread("loanAmount=" + loanAmount +"  interestRate=" + interestRate+"  interest=" + interest));

        // 创建一个法国的货币格式化实例
        //        NumberFormat n = NumberFormat.getCurrencyInstance(Locale.FRANCE);
        //金硕:	¥60,000.88  todo 当前环境变量是中国  所以用人民币格式展示
        ZhouxxTool.printTimeAndThread(ZhouxxTool.getTimeAndThread("金硕:\t"+ currency.format(loanAmount)));
        // 利率:	0.8%
        ZhouxxTool.printTimeAndThread(ZhouxxTool.getTimeAndThread("利率:\t"+ percent.format(interestRate)));
        // 利息:	¥480.01  todo 当前环境变量是中国  所以用人民币格式展示  todo这里还默认四舍五入了
        ZhouxxTool.printTimeAndThread(ZhouxxTool.getTimeAndThread("利息:\t"+currency.format(interest)));
    }
}

BigDecimalUtil工具类

java 复制代码
package com.zxx.study.base.scale;

import org.apache.commons.lang3.ObjectUtils;

import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.util.Objects;

/**
 * 1. 简介
 * 在Java中,我们经常需要进行精确的浮点数运算。然而,由于计算机的二进制表示方式和十进制表示方式之间的差异,导致了在浮点数计算中可能存在精度丢失的问题。为了解决这个问题,Java提供了BigDecimal类,它可以处理任意精度的浮点数运算。
 *
 * BigDecimal类是Java标准库中的一部分,它提供了大量的方法和功能,用于进行高精度的数学运算。在本文中,我们将介绍如何使用BigDecimal类进行精确的计算,并提供了一些常用的工具方法。
 *
 * BigDecimal类位于java.math包中,它提供了一系列方法来进行超过16位有效位的精确运算。以下是一些常用的BigDecimal方法:
 *
 * 加法: add(BigDecimal) - 返回两个BigDecimal对象中的值相加的结果。
 * 减法: subtract(BigDecimal) - 返回两个BigDecimal对象中的值相减的结果。
 * 乘法: multiply(BigDecimal) - 返回两个BigDecimal对象中的值相乘的结果。
 * 除法: divide(BigDecimal) - 返回两个BigDecimal对象中的值相除的结果。
 * 整除: divideAndRemainder(BigDecimal) - 返回两个BigDecimal对象的值相除后的商和余数。例如,n.divideAndRemainder(m)可以判断n是否是m的整数倍数。
 * 比较大小: compareTo(BigDecimal) - 比较此BigDecimal与指定的BigDecimal的大小。
 * 取绝对值: abs() - 返回此BigDecimal的绝对值。
 * 四舍五入: setScale(int newScale, RoundingMode roundingMode) - 设置此BigDecimal的小数位数并四舍五入。
 * 截断: stripTrailingZeros() - 去掉此BigDecimal尾部多余的零。
 *
 * 2. BigDecimal 类的使用
 * 2.1 创建 BigDecimal 对象
 * 要创建一个BigDecimal对象,我们可以使用以下几种方式:
 *
 * 2.1.1 通过字符串创建
 * BigDecimal bd1 = new BigDecimal("123.45");
 * BigDecimal bd2 = new BigDecimal("67.89");
 * 2.1.2 通过整数值创建
 * BigDecimal bd3 = BigDecimal.valueOf(100);
 * BigDecimal bd4 = BigDecimal.valueOf(200);
 * 2.1.3 通过浮点数值创建
 * BigDecimal bd5 = BigDecimal.valueOf(3.14);
 * BigDecimal bd6 = BigDecimal.valueOf(2.71);
 * 2.2 常用方法
 * BigDecimal类提供了许多常用的方法,用于进行精确的数学运算。下面是一些常用的方法:
 *
 * 2.2.1 加法
 * BigDecimal sum = bd1.add(bd2);
 * 2.2.2 减法
 * BigDecimal diff = bd1.subtract(bd2);
 * 2.2.3 乘法
 * BigDecimal product = bd1.multiply(bd2);
 * 2.2.4 除法
 * BigDecimal quotient = bd1.divide(bd2, RoundingMode.HALF_UP);
 * 2.2.5 取余
 * BigDecimal remainder = bd1.remainder(bd2);
 * 2.2.6 比较大小
 * int result = bd1.compareTo(bd2);
 * 2.2.7 取绝对值
 * BigDecimal absoluteValue = bd1.abs();
 * 2.2.8 取最大值
 * BigDecimal max = bd1.max(bd2);
 * 2.2.9 取最小值
 * BigDecimal min = bd1.min(bd2);
 * 2.3 精度设置
 * 在进行浮点数计算时,我们经常需要设置精度,以控制小数点后的位数。BigDecimal类提供了setScale方法,用于设置精度。
 * BigDecimal result = bd1.divide(bd2, 4, RoundingMode.HALF_UP);
 * 上面的代码将结果保留四位小数,并且使用了HALF_UP舍入模式。
 *
 * 2.4 注意事项
 * 在使用BigDecimal进行浮点数计算时,需要注意以下几点:
 *
 * 避免使用浮点数进行计算,而是使用字符串、整数或BigDecimal对象。
 * 使用适当的舍入模式来处理精度问题。
 * 不要使用equals方法进行BigDecimal对象的相等比较,而应该使用compareTo方法。

 *
 * @author zhouxx
 * @create 2024-07-10 20:21
 */
public class BigDecimalUtil {

    public static final BigDecimal per = new BigDecimal("100");
    public static final BigDecimal thousand = new BigDecimal("1000");
    public static final int scaleDefault = 4; // 默认精度 小数点后4位
    public static final RoundingMode roundingModeDefault = RoundingMode.HALF_UP ; // 默认舍入模式 四舍五入

    /**
     * 多个BigDecimal数据相乘
     */
    public static BigDecimal multiply(BigDecimal from, BigDecimal... to) {
        BigDecimal result = safe(from);
        if (to != null) {
            for (BigDecimal t : to) {
                result = result.multiply(safe(t));
            }
        }
        return result;
    }

    /**
     * 多个字符串数据相乘
     */
    public static BigDecimal multiply(String from, String... to) {
        BigDecimal result = strToDecimalDefaultZero(from);
        if (to != null) {
            for (String t : to) {
                result = result.multiply(safe(strToDecimalDefaultZero(t)));
            }
        }
        return result;
    }

    /**
     * 多个BigDecimal数据相加
     */
    public static BigDecimal add(BigDecimal from, BigDecimal... to) {
        BigDecimal result = safe(from);
        if (to != null) {
            for (BigDecimal t : to) {
                result = result.add(safe(t));
            }
        }
        return result;
    }

    /**
     * 多个字符串相加
     */
    public static String add(String from, String... to) {
        BigDecimal result = strToDecimalDefaultZero(from);
        if (to != null) {
            for (String t : to) {
                result = add(result, t);
            }
        }
        return toString(result);
    }

    /**
     * 第一个为BigDecimal 第二个为字符串
     */
    public static BigDecimal add(BigDecimal from, String to) {
        BigDecimal result = from;
        if (to != null && !to.isEmpty()) {
            result = result.add(strToDecimalDefaultZero(to));
        }
        return result;
    }


    /**
     * 减法 多个减数
     */
    public static BigDecimal subtract(BigDecimal from, BigDecimal... to) {
        BigDecimal result = safe(from);
        if (to != null) {
            for (BigDecimal t : to) {
                result = result.subtract(safe(t));
            }
        }
        return result;
    }

    /**
     * 除法 保留两位小数 除数为0 则为0
     */
    public static BigDecimal divide2(BigDecimal from, BigDecimal to) {
        BigDecimal result = safe(from);
        if (to == null) {
            return result;
        } else if (to.compareTo(BigDecimal.ZERO) == 0) {
            return BigDecimal.ZERO;
        } else {
            return result.divide(to, 2, roundingModeDefault);
        }
    }

    /**
     * BigDecimal除法 然后设置scale 除数为0,为空都结果为null
     */
    public static BigDecimal divide3(BigDecimal from, BigDecimal to) {
        BigDecimal result = safe(from);
        if (to == null || to.compareTo(BigDecimal.ZERO) == 0) {
            return null;
        } else {
            return result.divide(to, 3, roundingModeDefault);
        }

    }

    /**
     * BigDecimal除法 然后设置scale 除数为0,为空都结果为0
     */
    public static BigDecimal divide(BigDecimal from, BigDecimal to, Integer scale, RoundingMode roundingMode) {
        BigDecimal result = safe(from);
        if (to == null) {
            return result;
        } else if (to.compareTo(BigDecimal.ZERO) == 0) {
            return BigDecimal.ZERO;
        } else {
            scale = scale < 0 ? scaleDefault : scale;
            return result.divide(to, scale, roundingMode);
        }
    }

    /**
     * 字符串除法 然后设置scale
     */
    public static BigDecimal divide(String from, String to, Integer scale, RoundingMode roundingMode) {
        BigDecimal decimalFrom = strToDecimalDefaultNull(from);
        BigDecimal decimalTo = strToDecimalDefaultNull(to);
        return divide(decimalFrom, decimalTo, scale, roundingMode);
    }

    /**
     * 计算百分比
     */
    public static BigDecimal dividePer(BigDecimal from, BigDecimal to) {
        // null运算 todo 返回null
        if (Objects.isNull(from)|| Objects.isNull(to)) {
            return null;
        } else {
            from = multiply(from, per);
            return divide(from, to, scaleDefault,roundingModeDefault);
        }
    }


    /**
     * 千分级 小数转千分比
     */
    public static BigDecimal multiplyThousand(BigDecimal baseVal) {
        BigDecimal safe = safe(baseVal);
        return multiply(safe, thousand);
    }

    /**
     * 千分级 转成千分小数
     */
    public static BigDecimal divideThousand(BigDecimal baseVal) {
        BigDecimal safe = safe(baseVal);
        return divide(safe, thousand, scaleDefault,roundingModeDefault);
    }

    /**
     * 百分比转成小数
     */
    public static BigDecimal divideHundred(BigDecimal baseVal) {
        BigDecimal safe = safe(baseVal);
        return divide(safe, per, scaleDefault,roundingModeDefault);
    }

    /**
     * 小数转成百分比的值
     */
    public static BigDecimal multiplyHundred(BigDecimal baseVal) {
        BigDecimal safe = safe(baseVal);
        return multiply(safe, per);
    }

    /**
     * 比较两个BigDecimal数据大小 返回int
     */
    public static int compareTo(BigDecimal from, BigDecimal to) {
        return safe(from).compareTo(safe(to));
    }

    /**
     * 比较两个BigDecimal数据大小 返回true false
     */
    public static boolean greaterThan(BigDecimal from, BigDecimal to) {
        return safe(from).compareTo(safe(to)) > 0;
    }

    /**
     * 比较两个字符串数据比较
     */
    public static int compareTo(String from, String to) {
        BigDecimal decimalFrom = strToDecimalDefaultZero(from);
        BigDecimal decimalTo = strToDecimalDefaultZero(to);
        return safe(decimalFrom).compareTo(safe(decimalTo));
    }

    /**
     * 数据为空则返回Zero
     */
    private static BigDecimal safe(BigDecimal target) {
        // todo 数据为空则返回Zero
        if (Objects.isNull(target)) {
            return BigDecimal.ZERO;
        } else {
            return target;
        }
    }

    /**
     * 字符串转BigDecimal 为空则返回Zero
     */
    public static BigDecimal strToDecimalDefaultZero(String s) {
        // todo 数据为空则返回Zero
        return strToDecimal(s, BigDecimal.ZERO);
    }

    /**
     * 字符串转BigDecimal 为空则返回空
     */
    public static BigDecimal strToDecimalDefaultNull(String s) {
        return strToDecimal(s, null);
    }

    /**
     * 字符串转BigDecimal 为空默认值defaultV
     */
    private static BigDecimal strToDecimal(String s, BigDecimal defaultV) {
        if (ObjectUtils.isEmpty(s)) {
            // todo 默认值为 null
            return defaultV;
        }
        try {
            // todo 默认精度4位小数  且四舍五入
            return new BigDecimal(s).setScale(scaleDefault, roundingModeDefault);
        } catch (Exception e) {
            /**
             * todo new BigDecimal(s).setScale 会抛出两种异常
             *
             *舍入模式 =7 ROUND_UNNECESSARY 会报算数异常 ArithmeticException -- if roundingMode==ROUND_UNNECESSARY and the specified scaling operation would require rounding.
             *s舍入模式 不在[0,7] 会报无效参数异常 IllegalArgumentException -- if roundingMode does not represent a valid rounding mode.
             * */
            // todo 精度设置异常  则返回null
            return defaultV;
        }
    }

    /**
     * BigDecimal值如果为空转null
     */
    public static BigDecimal decimalDefaultNull(BigDecimal val) {
        if (ObjectUtils.isEmpty(val)) {
            return null;
        }
        try {
            // // todo 默认精度4位小数  且四舍五入
            return val.setScale(scaleDefault, roundingModeDefault);
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * BigDecimal值如果为空转BigDecimal-ZERO
     */
    public static BigDecimal decimalDefaultZero(BigDecimal val) {
        return ObjectUtils.isEmpty(val) ? BigDecimal.ZERO : val;
    }

    /**
     * 转换为字符串 6位小数
     */
    public static String toString(BigDecimal decimal) {
        DecimalFormat decimalFormat = new DecimalFormat("#.######");
        decimalFormat.setGroupingUsed(false);
        return decimal == null ? null : decimalFormat.format(decimal);
    }

    /**
     * 根据pattern格式化decimal数据
     */
    public static String toString(BigDecimal decimal, String pattern) {
        DecimalFormat decimalFormat = new DecimalFormat(pattern);
        decimalFormat.setGroupingUsed(false);
        return decimal == null ? null : decimalFormat.format(decimal);
    }

    /**
     * 设置小数位数,四舍五入
     */
    public static BigDecimal setScale(BigDecimal bigDecimal, int scale) {
        if (bigDecimal == null) {
            return null;
        }
        scale = scale< 0 ? scaleDefault : scale;
        return bigDecimal.divide(new BigDecimal(1), scale, roundingModeDefault);
    }

    /**
     * 减法 减去多个值
     */
    public static BigDecimal subtract(String from, String... to) {
        BigDecimal result = strToDecimalDefaultZero(from);
        if (to != null) {
            for (String t : to) {
                result = result.subtract(strToDecimalDefaultZero(t));
            }
        }
        return result;
    }

    /**
     * 字符串数value 据转成BigDecimal 默认值defaultV
     */
    private static BigDecimal strToDecimalToDefaultV(String value, BigDecimal defaultV, int scale) {
        if (ObjectUtils.isEmpty(value)) {
            return defaultV;
        }
        try {
            return new BigDecimal(value).setScale(scale, RoundingMode.HALF_UP);
        } catch (Exception e) {
            return defaultV;
        }
    }

    /**
     * 设置对象里面的所有的BigDecimal,保留小数位数scale位
     */
    public static void handleBigDecimalScale(Object o, int scale) {
        Field[] fields = o.getClass().getDeclaredFields();
        for (Field field : fields) {
            try {
                field.setAccessible(true);
                if (field.getType() == BigDecimal.class) {
                    Object v = field.get(o);
                    if (v instanceof BigDecimal) {
                        BigDecimal b = (BigDecimal) v;
                        b = setScale(b, scale);
                        field.set(o, b);
                    }
                }
            } catch (Exception ignored) {

            }
        }

    }

    /**
     * double-BigDecimal scale位小数
     */
    public static BigDecimal doubleValueSetScale(double value, int scale) {
        return setScale(new BigDecimal(value), scale);
    }
}
相关推荐
Yaml42 小时前
Spring Boot 与 Vue 共筑二手书籍交易卓越平台
java·spring boot·后端·mysql·spring·vue·二手书籍
小小小妮子~2 小时前
Spring Boot详解:从入门到精通
java·spring boot·后端
hong1616882 小时前
Spring Boot中实现多数据源连接和切换的方案
java·spring boot·后端
aloha_7892 小时前
从零记录搭建一个干净的mybatis环境
java·笔记·spring·spring cloud·maven·mybatis·springboot
记录成长java3 小时前
ServletContext,Cookie,HttpSession的使用
java·开发语言·servlet
睡觉谁叫~~~3 小时前
一文解秘Rust如何与Java互操作
java·开发语言·后端·rust
程序媛小果3 小时前
基于java+SpringBoot+Vue的旅游管理系统设计与实现
java·vue.js·spring boot
小屁孩大帅-杨一凡4 小时前
java后端请求想接收多个对象入参的数据
java·开发语言
java1234_小锋4 小时前
使用 RabbitMQ 有什么好处?
java·开发语言
TangKenny4 小时前
计算网络信号
java·算法·华为