大数计算与精度
约 846 字大约 3 分钟
布欧-Lewyon
2026-05-16
float 和 double 是浮点近似值,不适合需要精确计算的场景(金额、科学计算)。BigInteger 和 BigDecimal 提供任意精度的整数和小数运算,是金融和计费系统的基石。
浮点数的精度陷阱
// 你以为的结果:0.3
System.out.println(0.1 + 0.2); // 0.30000000000000004原因:double 无法精确表示 0.1(二进制无限循环小数)。金额计算永远不要使用 double 或 float。
BigDecimal
BigDecimal 提供任意精度的十进制运算。
创建
// 推荐:使用字符串构造器,避免精度损失
BigDecimal price = new BigDecimal("19.99");
// 不推荐:使用 double 构造器(仍会引入精度问题)
BigDecimal bad = new BigDecimal(0.1); // 0.1000000000000000055511...运算
BigDecimal a = new BigDecimal("10.50");
BigDecimal b = new BigDecimal("3.20");
BigDecimal sum = a.add(b); // 13.70
BigDecimal diff = a.subtract(b); // 7.30
BigDecimal product = a.multiply(b); // 33.6000
BigDecimal quotient = a.divide(b, 2, RoundingMode.HALF_UP); // 3.28divide必须指定精度(小数位数)和舍入模式,否则可能抛ArithmeticException(无限小数)。BigDecimal是不可变对象,每个运算都产生新实例。
常用舍入模式
| 模式 | 行为 | 示例(3.145 → 2 位小数) |
|---|---|---|
HALF_UP | 四舍五入 | 3.15 |
HALF_DOWN | 五舍六入 | 3.14 |
HALF_EVEN | 银行家舍入(向最近的偶数舍入) | 3.14 |
UP | 远离零方向舍入 | 3.15 |
DOWN | 向零方向舍入(截断) | 3.14 |
业务推荐:一般用 HALF_UP;金融行业用 HALF_EVEN(银行家舍入,减少累计偏差)。
比较
BigDecimal x = new BigDecimal("2.0");
BigDecimal y = new BigDecimal("2.00");
x.equals(y); // false(因为 scale 不同:1 vs 2)
x.compareTo(y); // 0(compareTo 忽略小数位尾随零,业务比较应使用它)始终用 compareTo 比较大小,equals 会比较精度(小数位数)。
格式化输出
BigDecimal amount = new BigDecimal("1234.5678");
String formatted = amount.setScale(2, RoundingMode.HALF_UP)
.toPlainString(); // "1234.57"
// 千分位
NumberFormat nf = NumberFormat.getCurrencyInstance(Locale.CHINA);
nf.setCurrency(Currency.getInstance("CNY"));
System.out.println(nf.format(amount)); // ¥1,234.57BigInteger
BigInteger 提供任意精度的整数运算,超出 long 范围时使用:
BigInteger huge = new BigInteger("123456789012345678901234567890");
BigInteger two = BigInteger.valueOf(2); // 从 long 转换
BigInteger sum = huge.add(two);
BigInteger product = huge.multiply(two);
BigInteger mod = huge.mod(new BigInteger("1000000"));
// 质数判断(密码学场景)
boolean probablyPrime = huge.isProbablePrime(100); // certainty 参数BigInteger也是不可变的,适合大数运算但比long慢一两个数量级。- 普通
int/long能解决的场景无需用BigInteger。
实战建议
- 金额存储:数据库用
DECIMAL(10,2),Java 用BigDecimal。 - 前后端传递:用字符串格式传递(避免 JSON 序列化精度丢失)。
equals比较:用compareTo而非equals。double转BigDecimal:优先BigDecimal.valueOf(double)(内部用Double.toString避免精度问题),而非new BigDecimal(double)。
小结
BigDecimal用于精确的十进制运算,永远不要用double表示金额。- 创建
BigDecimal优先用字符串构造器;divide必须指定精度和舍入模式。 compareTo比较值,equals同时比较精度(2.0 ≠ 2.00)。BigInteger处理超long范围的整数运算,不可变但性能低于基本类型。- 易错:
BigDecimal(double)构造器会引入浮点数本身的精度问题,如new BigDecimal(0.1)得到0.1000000000000000055511...。始终使用字符串构造器或BigDecimal.valueOf。 - 思考任务:实现一个金额计算工具方法,接受两个字符串金额和一个运算符(+、-、*、/),返回保留两位小数、四舍五入的结果字符串。
下一节:异常
