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

news2024/9/22 17:31:30

概叙

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

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

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

踩坑:浮点运算的精度坑

 1 - 0.8f  = 0.19999999

        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

        //
        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方法初始化,两种方式得到的结果不一样

     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相等(大小比较,不含精度)

    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进行计算时,结果一定要设置精度和舍入模式。

    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的值转成了科学计数法的值

    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进行格式化控制。

    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.默认值和默认精度设置

    /**
     * 字符串转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;
        }
    }

完整代码

踩坑代码

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工具类

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);
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1915883.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

PHP实现用户认证与权限管理的全面指南

目录 引言 1. 数据库设计 1.1 用户表&#xff08;users&#xff09; 1.2 角色表&#xff08;roles&#xff09; 1.3 权限表&#xff08;permissions&#xff09; 1.4 用户角色关联表&#xff08;user_roles&#xff09; 1.5 角色权限关联表&#xff08;role_permissions…

MySQL之基本查询(下)-表的增删查改

表的增删查改&#xff1a;CRUD : Create(创建), Retrieve(读取)&#xff0c;Update(更新)&#xff0c;Delete&#xff08;删除&#xff09; Update(更新) 语法&#xff1a; UPDATE table_name SET column expr [, column expr ...] [WHERE ...] [ORDER BY ...] [LIMIT ...] …

【观成科技】Websocket协议代理隧道加密流量分析与检测

Websocket协议代理隧道加密流量简介 攻防场景下&#xff0c;Websocket协议常被用于代理隧道的搭建&#xff0c;攻击者企图通过Websocket协议来绕过网络限制&#xff0c;搭建一个低延迟、双向实时数据传输的隧道。当前&#xff0c;主流的支持Websocket通信代理的工具有&#xf…

AnimateLCM:高效生成连贯真实的视频

视频扩散模型因其能够生成连贯且高保真的视频而日益受到关注。然而&#xff0c;迭代去噪过程使得这类模型计算密集且耗时&#xff0c;限制了其应用范围。香港中文大学 MMLab、Avolution AI、上海人工智能实验室和商汤科技公司的研究团队提出了AnimateLCM&#xff0c;这是一种允…

盲盒抽卡机小程序:抽卡机的多样化发展

近几年&#xff0c;盲盒卡牌出现在了大众的生活中&#xff0c;深受学生和年轻消费者的喜爱。卡牌是一种新的盲盒模式&#xff0c;玩家购买后随机获得卡牌&#xff0c;为了收集一整套卡牌&#xff0c;玩家会进行各种复购行为&#xff0c;卡牌逐渐成为了年轻人追捧的休闲方式&…

获取天气数据

获取天气数据其实是一个简单的HTTP接口&#xff0c;根据用户输入的adcode&#xff0c;查询目标区域当前/未来的天气数据&#xff0c;数据来源是中国气象局。 第一步&#xff0c;申请”web服务 API”密钥&#xff08;Key&#xff09;&#xff1b; 链接: 首页 | 高德控制台 (am…

HTTP协议分析/burp/goby/xray

一、HTTP简介 HTTP(超文本传输协议)是今天所有web应用程序使用的通信协议。最初&#xff0c;HTTP只是一个为获取基于文本的静态资源而开发的简单协议&#xff0c;后来人们以名种形式扩展和利用它.使其能够支持如今常见的复杂分布式应用程序。HTTP使用一种用于消息的模型:客户端…

软件产品必须进行确认测试吗?包括哪些测试流程和注意事项?

在当前科技快速发展的时代&#xff0c;软件产品已经成为人们生活和工作中不可或缺的一部分。然而&#xff0c;随着软件产品的增多和复杂性的提升&#xff0c;软件质量的问题也逐渐浮现出来。为了确保软件产品的质量和稳定性&#xff0c;软件产品在开发完成后必须进行确认测试。…

模版初阶(更新)

文章目录 模版介绍函数模版模版匹配规则类模版结言 模版介绍 函数模版分为两个类型&#xff1a; 函数模版类模版 函数模版 语法格式&#xff1a; t e m p l a t e < t y p n a m e T 1 , t y p n a m e T 2... > template<typname T1,typname T2...> template&…

小技巧(更新中)

1.Pycharm使用小技巧pycharm的使用小技巧1---快速找到模块内的函数和类&#xff0c;快速定位查看的模块所在位置_pycharm怎么查找某个函数-CSDN博客 2. Python库之requirments Python库安装之requirements.txt, environment.yml_python requirements-CSDN博客 3.执行.sh脚本的…

N6 word2vec文本分类

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊# 前言 前言 上周学习了训练word2vec模型&#xff0c;这周进行相关实战 1. 导入所需库和设备配置 import torch import torch.nn as nn import torchvision …

10x Visium HD数据分析

–https://satijalab.org/seurat/articles/visiumhd_analysis_vignette 留意更多内容&#xff0c;欢迎关注微信公众号&#xff1a;组学之心 1.数据准备-Seurat Visium HD 数据是由特定空间排列分布的寡核苷酸序列在 2um x 2um 的网格&#xff08;bin&#xff09;中生成的。然…

15. Revit API: Transaction(事务)与 Failures(故障处理)

前言 UI讲完&#xff0c;回到DB这块儿。在Document那篇&#xff0c;提到增删改查操作都是在Document上&#xff0c;是对Documet进行操作。 看到“增删改查”这四个&#xff0c;想到什么了没有&#xff1f; 数据库&#xff08;DB&#xff09;嘛~话说那本经典的红皮数据库的书叫…

Python学习笔记34:进阶篇(二十三)pygame的使用之颜色与字体

前言 基础模块的知识通过这么长时间的学习已经有所了解&#xff0c;更加深入的话需要通过完成各种项目&#xff0c;在这个过程中逐渐学习&#xff0c;成长。 我们的下一步目标是完成python crash course中的外星人入侵项目&#xff0c;这是一个2D游戏项目。在这之前&#xff…

算法训练营day28--134. 加油站 +135. 分发糖果+860.柠檬水找零+406.根据身高重建队列

一、 134. 加油站 题目链接&#xff1a;https://leetcode.cn/problems/gas-station/ 文章讲解&#xff1a;https://programmercarl.com/0134.%E5%8A%A0%E6%B2%B9%E7%AB%99.html 视频讲解&#xff1a;https://www.bilibili.com/video/BV1jA411r7WX 1.1 初见思路 得模拟分析出…

【Python实战因果推断】19_线性回归的不合理效果9

目录 De-Meaning and Fixed Effects Omitted Variable Bias: Confounding Through the Lens of Regression De-Meaning and Fixed Effects 您刚刚看到了如何在模型中加入虚拟变量来解释不同组间的不同干预分配。但是&#xff0c;FWL 定理真正的亮点在于虚拟变量。如果您有大量…

鸿蒙架构之AOP

零、主要内容 AOP 简介ArkTs AOP 实现原理 JS 原型链AOP实现原理 AOP的应用场景 统计类&#xff1a; 方法调用次数统计、方法时长统计防御式编程&#xff1a;参数校验代理模式实现 AOP的注意事项 一、AOP简介 对于Android、Java Web 开发者来说&#xff0c; AOP编程思想并不…

【前端】包管理器:npm、Yarn 和 pnpm 的全面比较

前端开发中的包管理器&#xff1a;npm、Yarn 和 pnpm 的全面比较 在现代前端开发中&#xff0c;包管理器是开发者必不可少的工具。它们不仅能帮我们管理项目的依赖&#xff0c;还能极大地提高开发效率。本文将详细介绍三种主流的前端包管理器&#xff1a;npm、Yarn 和 pnpm&am…

错位情缘悬疑升级

✨&#x1f525;【错位情缘&#xff0c;悬疑升级&#xff01;关芝芝与黄牡丹的惊世婚约】&#x1f525;✨在这个迷雾重重的剧场&#xff0c;一场前所未有的错位大戏正悄然上演&#xff01;&#x1f440; 你没看错&#xff0c;昔日兄弟的前女友关芝芝&#xff0c;竟摇身一变成了…

axios使用sm2加密数据后请求参数多了双引号解决方法

axios使用sm2加密数据后请求参数多了双引号解决 背景问题描述解决过程 背景 因项目安全要求&#xff0c;需对传给后端的入参加密&#xff0c;将请求参数加密后再传给后端 前期将axios降低到1.6.7后解决了问题&#xff0c;但最近axios有漏洞&#xff0c;安全要求对版本升级&…