1. 概述
本文将深入探讨Java中的BigDecimal
和BigInteger
两个类。我们将介绍这两种数据类型的特性、适用场景,并演示基本操作方法。这些类在需要高精度计算的场景中尤为重要。
2. BigDecimal
BigDecimal
表示不可变的、任意精度的有符号十进制数。它由两部分组成:
- 非标度值:一个任意精度的整数
- 标度:一个32位整数,表示小数点后的位数
例如,BigDecimal
3.14的非标度值是314,标度是2。
**我们主要在以下场景使用BigDecimal
**:
✅ 高精度算术运算(如金融交易计算)
✅ 需要控制标度和舍入行为的场景
创建BigDecimal实例
我们可以通过多种方式创建BigDecimal
对象:
@Test
public void whenBigDecimalCreated_thenValueMatches() {
BigDecimal bdFromString = new BigDecimal("0.1");
BigDecimal bdFromCharArray = new BigDecimal(new char[] {'3','.','1','6','1','5'});
BigDecimal bdlFromInt = new BigDecimal(42);
BigDecimal bdFromLong = new BigDecimal(123412345678901L);
BigInteger bigInteger = BigInteger.probablePrime(100, new Random());
BigDecimal bdFromBigInteger = new BigDecimal(bigInteger);
assertEquals("0.1",bdFromString.toString());
assertEquals("3.1615",bdFromCharArray.toString());
assertEquals("42",bdlFromInt.toString());
assertEquals("123412345678901",bdFromLong.toString());
assertEquals(bigInteger.toString(),bdFromBigInteger.toString());
}
⚠️ 踩坑警告:避免使用double
构造函数!
@Test
public void whenBigDecimalCreatedFromDouble_thenValueMayNotMatch() {
BigDecimal bdFromDouble = new BigDecimal(0.1d);
assertNotEquals("0.1", bdFromDouble.toString());
}
为什么?因为:
double
构造函数会进行精确转换0.1
在double
中无法精确表示
推荐使用valueOf
静态方法:
@Test
public void whenBigDecimalCreatedUsingValueOf_thenValueMatches() {
BigDecimal bdFromLong1 = BigDecimal.valueOf(123412345678901L);
BigDecimal bdFromLong2 = BigDecimal.valueOf(123412345678901L, 2);
BigDecimal bdFromDouble = BigDecimal.valueOf(0.1d);
assertEquals("123412345678901", bdFromLong1.toString());
assertEquals("1234123789.01", bdFromLong2.toString());
assertEquals("0.1", bdFromDouble.toString());
}
3. BigDecimal的操作
与其他数值类(Integer
, Long
, Double
等)类似,BigDecimal
提供算术和比较操作,但不重载运算符(+、-、*、/)。我们需要使用对应方法:
add()
加法subtract()
减法multiply()
乘法divide()
除法compareTo()
比较
获取属性
@Test
public void whenGettingAttributes_thenExpectedResult() {
BigDecimal bd = new BigDecimal("-12345.6789");
assertEquals(9, bd.precision()); // 精度
assertEquals(4, bd.scale()); // 标度
assertEquals(-1, bd.signum()); // 符号(负数返回-1)
}
比较操作
compareTo
方法忽略标度比较数值:
@Test
public void whenComparingBigDecimals_thenExpectedResult() {
BigDecimal bd1 = new BigDecimal("1.0");
BigDecimal bd2 = new BigDecimal("1.00");
BigDecimal bd3 = new BigDecimal("2.0");
assertTrue(bd1.compareTo(bd3) < 0); // 小于
assertTrue(bd3.compareTo(bd1) > 0); // 大于
assertTrue(bd1.compareTo(bd2) == 0); // 相等
assertTrue(bd1.compareTo(bd3) <= 0); // 小于等于
assertTrue(bd1.compareTo(bd2) >= 0); // 大于等于
assertTrue(bd1.compareTo(bd3) != 0); // 不等于
}
❌ 而equals
方法同时比较数值和标度:
@Test
public void whenEqualsCalled_thenSizeAndScaleMatched() {
BigDecimal bd1 = new BigDecimal("1.0");
BigDecimal bd2 = new BigDecimal("1.00");
assertFalse(bd1.equals(bd2)); // 标度不同,不相等
}
算术运算
@Test
public void whenPerformingArithmetic_thenExpectedResult() {
BigDecimal bd1 = new BigDecimal("4.0");
BigDecimal bd2 = new BigDecimal("2.0");
BigDecimal sum = bd1.add(bd2); // 加法
BigDecimal difference = bd1.subtract(bd2); // 减法
BigDecimal quotient = bd1.divide(bd2); // 除法
BigDecimal product = bd1.multiply(bd2); // 乘法
assertTrue(sum.compareTo(new BigDecimal("6.0")) == 0);
assertTrue(difference.compareTo(new BigDecimal("2.0")) == 0);
assertTrue(quotient.compareTo(new BigDecimal("2.0")) == 0);
assertTrue(product.compareTo(new BigDecimal("8.0")) == 0);
}
重要特性:由于BigDecimal
不可变,所有操作都返回新对象,不会修改原对象。
4. 舍入与BigDecimal
舍入是用更简短、更有意义的表示替换数字的过程。例如将$24.784917舍入为$24.78(因为货币没有更小单位)。
舍入模式
Java提供RoundingMode
枚举定义8种舍入模式:
CEILING
- 向正无穷方向舍入FLOOR
- 向负无穷方向舍入UP
- 远离零方向舍入DOWN
- 向零方向舍入HALF_UP
- 四舍五入(最常用)HALF_DOWN
- 五舍六入HALF_EVEN
- 银行家舍入(最公平)UNNECESSARY
- 精确匹配,否则抛异常
⭐ HALF_EVEN
(银行家舍入)能最小化舍入偏差,是金融计算的首选。
MathContext
MathContext
封装精度和舍入模式,提供预定义常量:
DECIMAL32
- 7位精度,HALF_EVENDECIMAL64
- 16位精度,HALF_EVENDECIMAL128
- 34位精度,HALF_EVENUNLIMITED
- 无限精度
@Test
public void whenRoundingDecimal_thenExpectedResult() {
BigDecimal bd = new BigDecimal("2.5");
// 使用HALF_EVEN舍入到1位精度
BigDecimal rounded = bd
.round(new MathContext(1, RoundingMode.HALF_EVEN));
assertEquals("2", rounded.toString()); // 2.5舍入为2(银行家舍入)
}
实战案例:计算商品总价
public static BigDecimal calculateTotalAmount(BigDecimal quantity,
BigDecimal unitPrice, BigDecimal discountRate, BigDecimal taxRate) {
BigDecimal amount = quantity.multiply(unitPrice);
BigDecimal discount = amount.multiply(discountRate);
BigDecimal discountedAmount = amount.subtract(discount);
BigDecimal tax = discountedAmount.multiply(taxRate);
BigDecimal total = discountedAmount.add(tax);
// 舍入到2位小数(分),使用HALF_EVEN
BigDecimal roundedTotal = total.setScale(2, RoundingMode.HALF_EVEN);
return roundedTotal;
}
测试用例:
@Test
public void givenPurchaseTxn_whenCalculatingTotalAmount_thenExpectedResult() {
BigDecimal quantity = new BigDecimal("4.5");
BigDecimal unitPrice = new BigDecimal("2.69");
BigDecimal discountRate = new BigDecimal("0.10");
BigDecimal taxRate = new BigDecimal("0.0725");
BigDecimal amountToBePaid = BigDecimalDemo
.calculateTotalAmount(quantity, unitPrice, discountRate, taxRate);
assertEquals("11.68", amountToBePaid.toString());
}
5. BigInteger
BigInteger
表示不可变的任意精度整数。类似于基本整数类型,但支持任意大的数值。
使用场景:
✅ 数值超过long
范围时(如50的阶乘)
✅ 安全加密应用
✅ 大数计算(如密码学)
创建BigInteger实例
@Test
public void whenBigIntegerCreatedFromConstructor_thenExpectedResult() {
BigInteger biFromString = new BigInteger("1234567890987654321");
BigInteger biFromByteArray = new BigInteger(
new byte[] { 64, 64, 64, 64, 64, 64 });
BigInteger biFromSignMagnitude = new BigInteger(-1,
new byte[] { 64, 64, 64, 64, 64, 64 });
assertEquals("1234567890987654321", biFromString.toString());
assertEquals("70644700037184", biFromByteArray.toString());
assertEquals("-70644700037184", biFromSignMagnitude.toString());
}
推荐使用valueOf
方法转换long
:
@Test
public void whenLongConvertedToBigInteger_thenValueMatches() {
BigInteger bi = BigInteger.valueOf(2305843009213693951L);
assertEquals("2305843009213693951", bi.toString());
}
6. BigInteger的操作
比较操作
@Test
public void givenBigIntegers_whentCompared_thenExpectedResult() {
BigInteger i = new BigInteger("123456789012345678901234567890");
BigInteger j = new BigInteger("123456789012345678901234567891");
BigInteger k = new BigInteger("123456789012345678901234567892");
assertTrue(i.compareTo(i) == 0); // 相等
assertTrue(j.compareTo(i) > 0); // 大于
assertTrue(j.compareTo(k) < 0); // 小于
}
算术运算
@Test
public void givenBigIntegers_whenPerformingArithmetic_thenExpectedResult() {
BigInteger i = new BigInteger("4");
BigInteger j = new BigInteger("2");
BigInteger sum = i.add(j); // 加法
BigInteger difference = i.subtract(j); // 减法
BigInteger quotient = i.divide(j); // 除法
BigInteger product = i.multiply(j); // 乘法
assertEquals(new BigInteger("6"), sum);
assertEquals(new BigInteger("2"), difference);
assertEquals(new BigInteger("2"), quotient);
assertEquals(new BigInteger("8"), product);
}
核心优势:不会发生溢出!而int
/long
运算可能溢出。
位运算操作
@Test
public void givenBigIntegers_whenPerformingBitOperations_thenExpectedResult() {
BigInteger i = new BigInteger("17");
BigInteger j = new BigInteger("7");
BigInteger and = i.and(j); // 按位与
BigInteger or = i.or(j); // 按位或
BigInteger not = j.not(); // 按位非
BigInteger xor = i.xor(j); // 按位异或
BigInteger andNot = i.andNot(j); // 与非
BigInteger shiftLeft = i.shiftLeft(1); // 左移
BigInteger shiftRight = i.shiftRight(1); // 右移
assertEquals(new BigInteger("1"), and);
assertEquals(new BigInteger("23"), or);
assertEquals(new BigInteger("-8"), not);
assertEquals(new BigInteger("22"), xor);
assertEquals(new BigInteger("16"), andNot);
assertEquals(new BigInteger("34"), shiftLeft);
assertEquals(new BigInteger("8"), shiftRight);
}
高级位操作
@Test
public void givenBigIntegers_whenPerformingBitManipulations_thenExpectedResult() {
BigInteger i = new BigInteger("1018");
int bitCount = i.bitCount(); // 1的位数
int bitLength = i.bitLength(); // 二进制长度
int getLowestSetBit = i.getLowestSetBit(); // 最低位1的位置
boolean testBit3 = i.testBit(3); // 测试第3位是否为1
BigInteger setBit12 = i.setBit(12); // 设置第12位为1
BigInteger flipBit0 = i.flipBit(0); // 翻转第0位
BigInteger clearBit3 = i.clearBit(3); // 清除第3位
assertEquals(8, bitCount);
assertEquals(10, bitLength);
assertEquals(1, getLowestSetBit);
assertEquals(true, testBit3);
assertEquals(new BigInteger("5114"), setBit12);
assertEquals(new BigInteger("1019"), flipBit0);
assertEquals(new BigInteger("1010"), clearBit3);
}
数论运算
@Test
public void givenBigIntegers_whenModularCalculation_thenExpectedResult() {
BigInteger i = new BigInteger("31");
BigInteger j = new BigInteger("24");
BigInteger k = new BigInteger("16");
BigInteger gcd = j.gcd(k); // 最大公约数
BigInteger multiplyAndmod = j.multiply(k).mod(i); // 模乘
BigInteger modInverse = j.modInverse(i); // 模逆
BigInteger modPow = j.modPow(k, i); // 模幂
assertEquals(new BigInteger("8"), gcd);
assertEquals(new BigInteger("12"), multiplyAndmod);
assertEquals(new BigInteger("22"), modInverse);
assertEquals(new BigInteger("7"), modPow);
}
质数操作
@Test
public void givenBigIntegers_whenPrimeOperations_thenExpectedResult() {
BigInteger i = BigInteger.probablePrime(100, new Random()); // 生成100位质数
boolean isProbablePrime = i.isProbablePrime(1000); // 质数检测
assertEquals(true, isProbablePrime);
}
7. 总结
BigDecimal
和BigInteger
是Java中处理高精度数值计算的核心工具:
- BigDecimal:适用于精确十进制运算(金融、货币计算)
- BigInteger:适用于超大整数运算(密码学、大数计算)
简单粗暴地说:当基本数值类型不够用时,就是这两个类大显身手的时候。掌握它们能帮你避开无数数值计算的坑!
完整源码可在GitHub获取。