本文目录导读:

- 核心原则:用对类
- 避免
BigDecimal构造误差 - 除法运算:指定舍入模式
- 比较相等:用
compareTo而非equals - 金融计算典型案例:避免 double 累积误差
- 避免
BigInteger的常见陷阱 - 性能与精度权衡(合理缓存)
- 完整案例:电商订单金额计算
- 总结:避免误差 checklist
在Java中处理大数运算时,避免误差的关键在于选择正确的数据类型,主要涉及 BigInteger(整数)和 BigDecimal(浮点数)两个类。
以下分场景说明如何避免误差,并附带典型案例。
核心原则:用对类
| 业务场景 | 推荐类 | 原因 |
|---|---|---|
| 精确整数(金额分、ID、密码学) | BigInteger |
任意精度,无上限 |
| 精确小数(金额元、科学计算) | BigDecimal |
避免 float/double 的二进制浮点误差 |
| 高性能、允许误差 | double / float |
不推荐用于金融、计费 |
避免 BigDecimal 构造误差
错误写法:
BigDecimal a = new BigDecimal(0.1); // 打印出来是 0.100000000000000005551115... BigDecimal b = new BigDecimal(0.2); BigDecimal sum = a.add(b); // 结果是 0.300000000000000044408920985006...
正确写法:
BigDecimal a = new BigDecimal("0.1"); // 使用字符串构造
BigDecimal b = new BigDecimal("0.2");
BigDecimal sum = a.add(b); // 结果是 0.3(精确)
// 或者使用静态方法 valueOf(推荐)
BigDecimal c = BigDecimal.valueOf(0.1);
BigDecimal d = BigDecimal.valueOf(0.2);
原理:
new BigDecimal(double) 会尝试用二进制精确表示 1,但 1 在二进制中是无限循环小数,导致构造时就已经产生了误差。字符串构造则直接解析十进制字符串,无中间转换误差。
除法运算:指定舍入模式
BigDecimal 的除法必须指定精度(scale) 和舍入模式,否则对于无限小数会抛出 ArithmeticException。
BigDecimal a = new BigDecimal("10");
BigDecimal b = new BigDecimal("3");
// 错误:会抛出 ArithmeticException
// BigDecimal result = a.divide(b);
// 正确:指定精度和舍入模式
BigDecimal result = a.divide(b, 2, RoundingMode.HALF_UP);
// 结果:3.33
常用舍入模式:
HALF_UP:四舍五入(银行家算法替代方案,金融常用)HALF_EVEN:银行家舍入法(四舍六入五成双,统计学最优)DOWN:直接截断(不向上进位)UP:远离零舍入(全部进位)
比较相等:用 compareTo 而非 equals
BigDecimal a = new BigDecimal("1.00");
BigDecimal b = new BigDecimal("1.0");
a.equals(b); // false(因为 scale 不同:2 vs 1)
a.compareTo(b) == 0; // true(忽略小数位数,只比较数值)
错误后果:
使用 equals 比较时,00 和 0 被视为不相等,可能导致校验失败。
金融计算典型案例:避免 double 累积误差
场景: 计算利息,日复利,每天增加 0.001 倍。
// ❌ 错误:使用 double
double amount = 100.0;
double rate = 0.001;
for (int i = 0; i < 365; i++) {
amount = amount * (1 + rate); // 累积误差越来越大
}
// ✅ 正确:使用 BigDecimal
BigDecimal amount = new BigDecimal("100");
BigDecimal rate = new BigDecimal("0.001");
BigDecimal one = BigDecimal.ONE;
for (int i = 0; i < 365; i++) {
amount = amount.multiply(one.add(rate));
}
避免 BigInteger 的常见陷阱
整数除法丢失精度:
BigInteger a = new BigInteger("10");
BigInteger b = new BigInteger("3");
// 正确:需要保留小数时,应使用 BigDecimal
BigDecimal result = new BigDecimal(a).divide(new BigDecimal(b), 10, RoundingMode.HALF_UP);
不必要的内存爆炸:
BigInteger 没有上限,但极端大数的运算(如 pow(1000000))会耗尽内存或极慢,应提前预估数据范围。
性能与精度权衡(合理缓存)
// 不要重复创建常量对象
BigDecimal zero = BigDecimal.ZERO;
BigDecimal one = BigDecimal.ONE;
BigDecimal ten = BigDecimal.TEN;
// 而不是
// BigDecimal zero = new BigDecimal("0"); // 每次创建新对象
完整案例:电商订单金额计算
public class OrderCalculator {
public static void main(String[] args) {
// 商品单价(元)
BigDecimal price = new BigDecimal("19.99");
// 购买数量
BigDecimal quantity = new BigDecimal("3");
// 折扣率 10%(0.9)
BigDecimal discountRate = new BigDecimal("0.9");
// 运费
BigDecimal shipping = new BigDecimal("5.00");
// 计算总价
BigDecimal subtotal = price.multiply(quantity);
BigDecimal discounted = subtotal.multiply(discountRate)
.setScale(2, RoundingMode.HALF_UP); // 保留2位小数
BigDecimal total = discounted.add(shipping);
System.out.println("总金额: " + total); // 输出:58.97
}
}
避免误差 checklist
| 要点 | 做法 |
|---|---|
构造 BigDecimal |
必须用字符串 "0.1",或用 valueOf(double) |
| 除法 | 必须指定 scale 和 RoundingMode |
| 比较 | 用 compareTo() 而不是 equals() |
| 浮点数运算 | 坚决不用 double 做金融计算 |
| 性能 | 缓存 ZERO/ONE/TEN,避免循环创建 |
| 溢出预防 | 大数运算前预估数据范围,避免内存耗尽 |
一句话:只要涉及金额、计费、精确科学计算,一律用 BigDecimal(字符串构造)并指定舍入模式。
版权声明:足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播