1. 概述

本文将深入探讨Java中的BigDecimalBigInteger两个类。我们将介绍这两种数据类型的特性、适用场景,并演示基本操作方法。这些类在需要高精度计算的场景中尤为重要。

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

为什么?因为:

  1. double构造函数会进行精确转换
  2. 0.1double中无法精确表示

推荐使用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种舍入模式:

  1. CEILING - 向正无穷方向舍入
  2. FLOOR - 向负无穷方向舍入
  3. UP - 远离零方向舍入
  4. DOWN - 向零方向舍入
  5. HALF_UP - 四舍五入(最常用)
  6. HALF_DOWN - 五舍六入
  7. HALF_EVEN - 银行家舍入(最公平)
  8. UNNECESSARY - 精确匹配,否则抛异常

HALF_EVEN(银行家舍入)能最小化舍入偏差,是金融计算的首选。

MathContext

MathContext封装精度和舍入模式,提供预定义常量:

  • DECIMAL32 - 7位精度,HALF_EVEN
  • DECIMAL64 - 16位精度,HALF_EVEN
  • DECIMAL128 - 34位精度,HALF_EVEN
  • UNLIMITED - 无限精度
@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. 总结

BigDecimalBigInteger是Java中处理高精度数值计算的核心工具:

  • BigDecimal:适用于精确十进制运算(金融、货币计算)
  • BigInteger:适用于超大整数运算(密码学、大数计算)

简单粗暴地说:当基本数值类型不够用时,就是这两个类大显身手的时候。掌握它们能帮你避开无数数值计算的坑!

完整源码可在GitHub获取。


原始标题:BigDecimal and BigInteger in Java | Baeldung