概叙
用过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);
}
}