概叙
用过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.8ZhouxxTool.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.NumberFormatExceptionBigDecimal a22 =BigDecimalUtil.strToDecimalDefaultNull(" 00 "); // todo 工具转换//BigDecimal a2 =new BigDecimal("0 0");// todo Exception in thread "main" java.lang.NumberFormatExceptionBigDecimal 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.NumberFormatExceptionBigDecimal c11 =BigDecimalUtil.strToDecimalDefaultNull("");// todo 工具转换BigDecimal c1 =new BigDecimal("0") ;//BigDecimal c2 =new BigDecimal(" ") ;// todo Exception in thread "main" java.lang.NumberFormatExceptionBigDecimal c22 =BigDecimalUtil.strToDecimalDefaultNull(" ");// todo 工具转换BigDecimal c2 =new BigDecimal("0") ;//BigDecimal c3 =new BigDecimal(" ") ; // todo Exception in thread "main" java.lang.NumberFormatExceptionBigDecimal c3 =new BigDecimal("01") ;// a22=null a222=null //todo 工具类全部将其置为 nullZhouxxTool.printTimeAndThread(ZhouxxTool.getTimeAndThread("a22=" + a22 +" a222=" + a222));// c11=null c22=null //todo 工具类全部将其置为 nullZhouxxTool.printTimeAndThread(ZhouxxTool.getTimeAndThread("c11=" + c11 +" c22=" + c22));//a1=0 a2=0.0ZhouxxTool.printTimeAndThread(ZhouxxTool.getTimeAndThread("a1=" + a1 +" a2=" + a2));//b1=null c1=0ZhouxxTool.printTimeAndThread(ZhouxxTool.getTimeAndThread("b1=" + b1+" c1=" + c1));//c2=0 c3=1ZhouxxTool.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.8ZhouxxTool.printTimeAndThread(ZhouxxTool.getTimeAndThread("a=" + a +" b=" + b+" c=" + c));// a-b=0.1999999999999999555910790149937383830547332763671875ZhouxxTool.printTimeAndThread(ZhouxxTool.getTimeAndThread("a-b=" + (a.subtract(b))));// a-c=0.2ZhouxxTool.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.0ZhouxxTool.printTimeAndThread(ZhouxxTool.getTimeAndThread("a=" + a +" b=" + b));// 设置了c的精度为2,RoundingMode.HALF_UP是向上舍入模式,即四舍五入。BigDecimal c=a.divide(b,2, RoundingMode.HALF_UP);// a/b=0.33ZhouxxTool.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.00704ZhouxxTool.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 数据为空则返回Zeroreturn 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 默认值为 nullreturn 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 精度设置异常 则返回nullreturn 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.8ZhouxxTool.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.NumberFormatExceptionBigDecimal a22 =BigDecimalUtil.strToDecimalDefaultNull(" 00 "); // todo 工具转换//BigDecimal a2 =new BigDecimal("0 0");// todo Exception in thread "main" java.lang.NumberFormatExceptionBigDecimal 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.NumberFormatExceptionBigDecimal c11 =BigDecimalUtil.strToDecimalDefaultNull("");// todo 工具转换BigDecimal c1 =new BigDecimal("0") ;//BigDecimal c2 =new BigDecimal(" ") ;// todo Exception in thread "main" java.lang.NumberFormatExceptionBigDecimal c22 =BigDecimalUtil.strToDecimalDefaultNull(" ");// todo 工具转换BigDecimal c2 =new BigDecimal("0") ;//BigDecimal c3 =new BigDecimal(" ") ; // todo Exception in thread "main" java.lang.NumberFormatExceptionBigDecimal c3 =new BigDecimal("01") ;// a22=null a222=null //todo 工具类全部将其置为 nullZhouxxTool.printTimeAndThread(ZhouxxTool.getTimeAndThread("a22=" + a22 +" a222=" + a222));// c11=null c22=null //todo 工具类全部将其置为 nullZhouxxTool.printTimeAndThread(ZhouxxTool.getTimeAndThread("c11=" + c11 +" c22=" + c22));//a1=0 a2=0.0ZhouxxTool.printTimeAndThread(ZhouxxTool.getTimeAndThread("a1=" + a1 +" a2=" + a2));//b1=null c1=0ZhouxxTool.printTimeAndThread(ZhouxxTool.getTimeAndThread("b1=" + b1+" c1=" + c1));//c2=0 c3=1ZhouxxTool.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.8ZhouxxTool.printTimeAndThread(ZhouxxTool.getTimeAndThread("a=" + a +" b=" + b+" c=" + c));// a-b=0.1999999999999999555910790149937383830547332763671875ZhouxxTool.printTimeAndThread(ZhouxxTool.getTimeAndThread("a-b=" + (a.subtract(b))));// a-c=0.2ZhouxxTool.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.0ZhouxxTool.printTimeAndThread(ZhouxxTool.getTimeAndThread("a=" + a +" b=" + b));// 设置了c的精度为2,RoundingMode.HALF_UP是向上舍入模式,即四舍五入。BigDecimal c=a.divide(b,2, RoundingMode.HALF_UP);// a/b=0.33ZhouxxTool.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.00704ZhouxxTool.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 返回nullif (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 数据为空则返回Zeroif (Objects.isNull(target)) {return BigDecimal.ZERO;} else {return target;}}/*** 字符串转BigDecimal 为空则返回Zero*/public static BigDecimal strToDecimalDefaultZero(String s) {// todo 数据为空则返回Zeroreturn 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 默认值为 nullreturn 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 精度设置异常 则返回nullreturn 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);}
}