BigDecimal类详解
# Java 中的 BigDecimal 类详解
在 Java 编程中,处理浮点数时经常会遇到精度问题。为了解决这个问题,Java 提供了一个 BigDecimal
类,它提供了精确的浮点数运算。本文将详细介绍 BigDecimal
类的使用方法和一些常见场景。
# DECIMAL(参数1, 参数2) 数据类型解析
以 DECIMAL(10, 2)
举例:
- 是一种数据类型,用于存储精确的小数。
- 总位数(Precision):第一个参数
10
表示整个数字的最大位数,包括小数点前后的所有数字。 - 小数位数(Scale):第二个参数
2
表示小数点后的位数。
具体来说,DECIMAL(10, 2)
表示该字段可以存储最多 10 位的数字,其中小数点后有 2 位。这意味着:
- 小数点前最多可以有 8 位数字。
- 小数点后固定有 2 位数字。
例如,以下是一些符合 DECIMAL(10, 2)
类型的数据示例:
1234567.89
0.01
12345.67
-1234.56
但以下数据不符合 DECIMAL(10, 2)
的定义:
123456789.01
(总位数超过 10 位)123456.7
(小数点后只有 1 位)-- 例如 mysql 会默认补0
使用 DECIMAL
类型的主要优点是它可以提供精确的小数存储和计算,这对于金融数据、货币金额等需要高精度的应用场景非常重要。与浮点数类型(如 FLOAT
和 DOUBLE
)不同,DECIMAL
不会引入舍入误差。
在你的数据库设计中,DECIMAL(10, 2)
通常用于存储价格、金额等数值,确保这些值能够被精确地存储和处理。
# 为什么使用 BigDecimal?
在 Java 中,float
和 double
类型的变量是基于 IEEE 754 标准的浮点数表示,这可能会导致精度损失。例如,0.1 无法在二进制浮点数中精确表示,这可能会导致计算结果与预期不符。而 BigDecimal
类则提供了任意精度的浮点数运算,非常适合处理金融数据。
# 创建 BigDecimal 对象
BigDecimal
对象可以通过多种方式创建:
- BigDecimal(int) 创建一个具有参数所指定整数值的对象。
- BigDecimal(double) 创建一个具有参数所指定双精度值的对象。 【不推荐使用】
- BigDecimal(long) 创建一个具有参数所指定长整数值的对象。
- BigDecimal(String) 创建一个具有参数所指定以字符串表示的数值的对象。【推荐使用】
@Test
void testConstructor() {
System.out.println("==================构造器==================");
// 通过 int 值构造
BigDecimal intBig = new BigDecimal(10);
System.out.println("intBig = " + intBig);
// 通过 double 值构造,会有精度问题(不推荐)
BigDecimal doubleBig = new BigDecimal(20.45);
System.out.println("doubleBig = " + doubleBig);
// 通过 long 值构造
BigDecimal longBig = new BigDecimal(30L);
System.out.println("longBig = " + longBig);
// 通过 String 值构造(推荐)
BigDecimal stringBig = new BigDecimal("123.45");
System.out.println("stringBig = " + stringBig);
// 将 double 值转换成 String 类型再构造
Double doubleValue = new Double(20.45);
BigDecimal doubleBig2 = new BigDecimal(doubleValue.toString());
System.out.println("doubleBig2 = " + doubleBig2);
// intBig = 10
// doubleBig = 20.449999999999999289457264239899814128875732421875
// longBig = 30
// stringBig = 123.45
// doubleBig2 = 20.45
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
也可以用 BigDecimal.valueOf()
方法:
BigDecimal multiplier = BigDecimal.valueOf(100);
# 获取小数位数
// 获取xia
int scale = amountDecimal.scale();
2
# 基本运算
BigDecimal
提供了一系列的方法来进行基本的数学运算,包括加法、减法、乘法和除法:
@Test
void testAsmd() {
System.out.println("==================加减乘除==================");
BigDecimal value1 = new BigDecimal("100.00");
BigDecimal value2 = new BigDecimal("12.34");
// 加法
BigDecimal sum = value1.add(value2); // 112.34
System.out.println("sum = " + sum);
// 减法
BigDecimal difference = value1.subtract(value2); // 87.66
System.out.println("difference = " + difference);
// 乘法
BigDecimal product = value1.multiply(value2); // 1234.0000
System.out.println("product = " + product);
// 除法(四舍五入,保留两位小数)
BigDecimal quotient = value1.divide(value2, 2, RoundingMode.HALF_UP); // 8.10
System.out.println("quotient = " + quotient);
// sum = 112.34
// difference = 87.66
// product = 1234.0000
// quotient = 8.10
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
在上面的例子中,divide
方法的第二个参数是小数位数,第三个参数是舍入模式。RoundingMode
是 BigDecimal
中的一个枚举,用于定义舍入行为。
# 舍入模式
- ROUND_UP :向远离 0 的方向舍入
- ROUND_DOWN :向零方向舍入
- ROUND_CEILING :向正无穷方向舍入
- ROUND_FLOOR :向负无穷方向舍入
- ROUND_HALF_UP :向(距离)最近的一边舍入,如果两边(的距离)是相等时,向上舍入, 1.55 保留一位小数结果为 1.6,也就是我们常说的 “
四舍五入
” - ROUND_HALF_DOWN :向(距离)最近的一边舍入,如果两边(的距离)是相等时,向下舍入, 例如1.55 保留一位小数结果为1.5
- ROUND_HALF_EVEN :向(距离)最近的一边舍入,如果两边(的距离)是相等时,如果保留位数是奇数,使用ROUND_HALF_UP,如果是偶数,使用ROUND_HALF_DOWN
- ROUND_UNNECESSARY :计算结果是精确的,不需要舍入模式
@Test
void testRoundingMode() {
System.out.println("==================舍入模式==================");
// 注意:断言请求的操作具有精确的结果,因此【不需要舍入】。
// 如果对获得精确结果的操作指定此舍入模式,则抛出 ArithmeticException。 -- 如果这里小数选择保留 1 位就会抛异常
BigDecimal bigDecimal = new BigDecimal("1.55").setScale(2, RoundingMode.UNNECESSARY);
System.out.println("Rounding mode: " + RoundingMode.UNNECESSARY + ", Value: " + bigDecimal);
testRoundingMode(new BigDecimal("1.55"), RoundingMode.values());
/* Rounding mode: UNNECESSARY, Value: 1.55
Rounding mode: UP, Value: 1.6
Rounding mode: DOWN, Value: 1.5
Rounding mode: CEILING, Value: 1.6
Rounding mode: FLOOR, Value: 1.5
Rounding mode: HALF_UP, Value: 1.6
Rounding mode: HALF_DOWN, Value: 1.5
Rounding mode: HALF_EVEN, Value: 1.6 */
}
private static void testRoundingMode(BigDecimal value, RoundingMode[] modes) {
for (RoundingMode mode : modes) {
BigDecimal roundedValue = value.setScale(1, mode);
System.out.printf("Rounding mode: %s, Value: %s%n", mode, roundedValue);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 比较操作
BigDecimal
类提供了比较两个对象的方法,这些方法返回一个整数,它表示调用对象与参数对象的相对大小:
@Test
void testCompare() {
System.out.println("==================比较操作==================");
BigDecimal value = new BigDecimal("100.00");
BigDecimal value1 = new BigDecimal("99.00");
BigDecimal value2 = new BigDecimal("100.00");
BigDecimal value3 = new BigDecimal("101.00");
// 比较值
int cmp1 = value.compareTo(value1);
int cmp2 = value.compareTo(value2);
int cmp3 = value.compareTo(value3);
System.out.println("cmp1 = " + cmp1); // 1
System.out.println("cmp2 = " + cmp2); // 0
System.out.println("cmp3 = " + cmp3); // -1
// 检查是否相等
boolean isEqual1 = value.equals(value1);
boolean isEqual2 = value.equals(value2);
boolean isEqual3 = value.equals(value3);
System.out.println("isEqual1 = " + isEqual1); // false
System.out.println("isEqual2 = " + isEqual2); // true
System.out.println("isEqual3 = " + isEqual3); // false
// 检查是否大于
boolean isGreaterThan1 = value.compareTo(value1) > 0;
boolean isGreaterThan2 = value.compareTo(value2) > 0;
boolean isGreaterThan3 = value.compareTo(value3) > 0;
System.out.println("isGreaterThan1 = " + isGreaterThan1); // true
System.out.println("isGreaterThan2 = " + isGreaterThan2); // false
System.out.println("isGreaterThan3 = " + isGreaterThan3); // false
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 格式化输出
DecimalFormat
类允许您自定义数字格式,包括小数点后的位数、分组分隔符、甚至前缀和后缀。
- 它是
java.text
包中的一个类,用于格式化十进制数字。
@Test
void testFormat() {
System.out.println("==================格式化输出==================");
DecimalFormat df = new DecimalFormat("#,##0.00");
double number1 = 123456.789;
double number2 = 1233123456.783;
String formattedNumber1 = df.format(number1); // 123,456.79
String formattedNumber2 = df.format(number2); // 1,233,123,456.78
System.out.println("Formatted Number1: " + formattedNumber1);
System.out.println("Formatted Number2: " + formattedNumber2);
}
// 这个例子展示了如何使用 DecimalFormat 对象来格式化一个 double 类型的数字,并添加了【千位分隔符】和【小数点后两位】的格式。
2
3
4
5
6
7
8
9
10
11
12
13
解析 "#,##0.00"
这个模式字符串:
#
:表示如果存在数字则显示数字。如果位数不足,它不会填充领先符号。在分组分隔符前的#
表示尽可能多地使用分组分隔符。,
:表示分组分隔符,通常用于分隔千位、百万位等。在这个模式中,它会在每三位数字后添加一个逗号。##0
:表示至少有一个有效数字(非零数字),如果数字不足,它将用零填充。##
表示如果存在数字则显示数字,如果不存在则不显示。这里的0
表示如果数字不足,将用零填充。.00
:表示小数点后有两位固定位数,即使它们是零。
综上所述,"#,##0.00"
这个模式会将数字格式化为带有千位分隔符的格式,并且小数点后总是有两位小数,如果小数部分不足两位,则用零填充。
# 处理精度问题
在使用 BigDecimal
时,需要注意精度问题。例如,直接使用 double
构造器可能会引入初始的精度问题:
// 错误的示例,可能会有精度问题
BigDecimal bd = new BigDecimal(0.1);
2
正确的做法是使用字符串构造器:
// 正确的示例
BigDecimal bd = new BigDecimal("0.1");
2
# 不同写法对比
/**
* 不同写法对比
* <p>
* 浮点数(如 float 和 double)在计算机中是以二进制形式存储的。某些十进制小数无法精确地表示为二进制小数,这会导致精度丢失
* </p>
* 使用 float 和 double:适用于对精度要求不是特别高的情况,但要注意它们的精度限制
* 使用 BigDecimal:适用于需要高精度计算的情况,尤其是金融计算等对精度要求很高的场景
*/
@Test
void test() {
int total = 10 + 21 + 31;
// 用 float 类型会丢失精度,不推荐
String format1 = String.format("%.1f", (float) 10 / total * 100);
System.out.println(format1);
// 用 double 类型也会丢失精度,比 float 更加安全,但还是不推荐
String format2 = String.format("%.1f", (double) 10 / total * 100);
System.out.println(format2);
// 推荐使用 BigDecimal
BigDecimal numerator = new BigDecimal(10);
BigDecimal denominator = new BigDecimal(total);
BigDecimal oneHundred = new BigDecimal(100);
BigDecimal percentage = numerator
.divide(denominator, 4, RoundingMode.HALF_UP)
.multiply(oneHundred);
String format3 = String.format("%.1f", percentage.doubleValue());
System.out.println(format3);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 结论
BigDecimal
类是 Java 中处理精确数值计算的重要工具。
- 它提供了丰富的 API 来执行各种数学运算,并能够避免
float
和double
类型的精度问题。 - 在处理金融数据或需要精确计算的场景中,
BigDecimal
是首选的数据类型。 - 推荐使用
字符串
构造器来创建BigDecimal
对象,以确保精度不受损失。