Java 的金额计算用 long 还是 BigDecimal?资深程序员这样选

张开发
2026/4/16 21:14:19 15 分钟阅读

分享文章

Java 的金额计算用 long 还是 BigDecimal?资深程序员这样选
前言最近接触一个新项目发现系统中所有金额相关字段都使用long类型来表示。作为一个习惯使用BigDecimal处理金额的开发者这让我产生了疑惑这会不会有精度问题为什么要这样设计“用double不行吗它也能表示小数啊”经过一番研究和思考把最终得出的结论来和大家详细分享一下。一、double、float类型比较double和float都是Java中的浮点数类型它们采用IEEE 754标准来表示小数。这种表示方法类似于科学计数法但存在一个致命问题不是所有小数都能精确表示。举一个简单的例子double a 0.1; double b 0.2; double result a b; System.out.println(result); // 输出0.30000000000000004什么简单的0.1 0.2居然不等于0.3是的这是因为在二进制的计算中有些十进制小数没有办法精确表示就像1除以3一样在十进制中也不能精确表示0.33333...。二、BigDecimal 解决方案BigDecimal的正确用法BigDecimal可以解决精度问题但必须正确使用。错误的构造方式BigDecimal wrong1 new BigDecimal(0.1); BigDecimal wrong2 new BigDecimal(0.2);这种传入浮点型的构造方式还是会有精度问题。正确的构造方式BigDecimal correct1 new BigDecimal(0.1); BigDecimal correct2 new BigDecimal(0.2); System.out.println(正确方式: correct1.add(correct2)); // 0.3通过传入字符串进行构造。或者用valueOf内部也是转字符串BigDecimal safe1 BigDecimal.valueOf(0.1); BigDecimal safe2 BigDecimal.valueOf(0.2); System.out.println(安全方式: safe1.add(safe2)); // 0.3BigDecimal的运算规则public class BigDecimalOperations { public static void main(String[] args) { BigDecimal price new BigDecimal(19.99); BigDecimal quantity new BigDecimal(3); BigDecimal taxRate new BigDecimal(0.13); // 乘法 BigDecimal subtotal price.multiply(quantity); // 除法必须指定精度和舍入模式 BigDecimal tax subtotal.multiply(taxRate) .setScale(2, RoundingMode.HALF_UP); // 加法 BigDecimal total subtotal.add(tax); System.out.println(小计: subtotal); // 59.97 System.out.println(税金: tax); // 7.80 System.out.println(总计: total); // 67.77 } }BigDecimal的比较操作public class BigDecimalComparison { public static void main(String[] args) { BigDecimal num1 new BigDecimal(10.00); BigDecimal num2 new BigDecimal(10.000); // 错误比较引用 System.out.println( 比较: (num1 num2)); // false // 错误equals会比较精度 System.out.println(equals比较: num1.equals(num2)); // false // 正确compareTo只比较数值 System.out.println(compareTo比较: (num1.compareTo(num2) 0)); // true } }BigDecimal的缺点性能开销对象创建和运算成本高内存占用每个对象需要20-30字节使用复杂容易用错构造方法和运算规则代码冗长简单的运算也需要多行代码三、long方案核心思想以分为单位存储使用long表示金额的核心思路是不以元为单位而以最小货币单位分存储。public class LongAmountDemo { // 工具方法元转分 public static long yuanToFen(double yuan) { return Math.round(yuan * 100); } // 工具方法分转元用于显示 public static String fenToYuanDisplay(long fen) { return String.format(¥%.2f, fen / 100.0); } // 工具方法分转元用于计算 public static double fenToYuan(long fen) { return fen / 100.0; } public static void main(String[] args) { // 以分为单位存储所有金额 long price yuanToFen(19.99); // 1999分 19.99元 long quantity 3; long taxRate 13; // 13% 0.13这里用整数表示百分比 // 计算过程全部使用整数运算 long subtotal price * quantity; // 5997分 long tax (subtotal * taxRate) / 100; // 780分 long total subtotal tax; // 6777分 System.out.println(小计: fenToYuanDisplay(subtotal)); // ¥59.97 System.out.println(税金: fenToYuanDisplay(tax)); // ¥7.80 System.out.println(总计: fenToYuanDisplay(total)); // ¥67.77 } }long方案的优势1.绝对精确整数运算在计算机中是精确的不会出现浮点数的精度问题。2.性能卓越// long运算 - 机器指令级别极快 long a 1000L, b 2000L; long result a b; // BigDecimal运算 - 方法调用对象创建较慢 BigDecimal c new BigDecimal(10.00); BigDecimal d new BigDecimal(20.00); BigDecimal result2 c.add(d);3.存储高效long固定8字节BigDecimal对象头数值通常20-30字节4.序列化简单在数据库、JSON、网络传输中处理更简单。实际业务应用public class OrderService { // 订单金额分 private long orderAmount; // 优惠金额分 private long discountAmount; // 实付金额分 private long actualAmount; public void calculateOrder(long unitPrice, int quantity, long discountRate) { // 计算订单金额 orderAmount unitPrice * quantity; // 计算优惠金额 discountAmount (orderAmount * discountRate) / 100; // 计算实付金额 actualAmount orderAmount - discountAmount; } // 显示方法 public String getDisplayAmount() { return String.format(订单金额: %s, 优惠: %s, 实付: %s, formatFen(orderAmount), formatFen(discountAmount), formatFen(actualAmount)); } private String formatFen(long fen) { return String.format(¥%.2f, fen / 100.0); } }四、各种方案的适用场景double/float绝对不要用于金额科学计算、图形处理统计分析允许误差物理模拟不适用于任何金额计算BigDecimal复杂金融计算高精度利率、汇率计算税务计算需要复杂小数运算银行核心系统需要任意精度的场景long大多数业务系统电商订单系统支付系统账户余额管理积分、优惠券系统五、BigDecimal的最佳实践如果确实需要使用BigDecimal请遵循以下规则1. 构造方法// 推荐 BigDecimal a new BigDecimal(0.1); BigDecimal b BigDecimal.valueOf(0.1); // 内部转字符串 // 避免 BigDecimal c new BigDecimal(0.1); // 精度问题2. 运算控制BigDecimal num1 new BigDecimal(10.00); BigDecimal num2 new BigDecimal(3.00); // 除法必须指定精度 BigDecimal result num1.divide(num2, 4, RoundingMode.HALF_UP); // 乘法建议控制精度 BigDecimal product num1.multiply(num2).setScale(2, RoundingMode.HALF_UP);3. 数值比较BigDecimal amount1 new BigDecimal(100.00); BigDecimal amount2 new BigDecimal(100.000); // 正确 if (amount1.compareTo(amount2) 0) { // 数值相等 } // 错误 if (amount1.equals(amount2)) { // 不会执行因为精度不同 }六、long方案的实施建议1. 建立工具类public class MoneyUtils { private MoneyUtils() {} // 工具类防止实例化 // 元转分 public static long yuanToFen(double yuan) { return Math.round(yuan * 100); } // 分转元显示用 public static String fenToDisplayYuan(long fen) { return String.format(¥%.2f, fen / 100.0); } // 分转元计算用 public static double fenToYuan(long fen) { return fen / 100.0; } // 金额加法防止溢出 public static long add(long amount1, long amount2) { return Math.addExact(amount1, amount2); } // 金额乘法 public static long multiply(long amount, int multiplier) { return Math.multiplyExact(amount, multiplier); } }2. 数据库设计-- 使用bigint存储金额分 CREATE TABLE orders ( id BIGINT PRIMARY KEY, order_amount BIGINT COMMENT 订单金额分, discount_amount BIGINT COMMENT 优惠金额分, actual_amount BIGINT COMMENT 实付金额分 );3. API设计// 请求和响应中使用Long类型表示金额分 public class OrderRequest { private Long productId; private Integer quantity; private Long unitPrice; // 单价分 } public class OrderResponse { private String orderNo; private Long totalAmount; // 总金额分 private String displayAmount; // 显示金额 ¥99.99 }总结与选择为什么那个项目选择long现在我可以理解那个项目的设计思路了业务特性主要是简单的加减乘运算没有复杂的小数除法性能要求高并发场景需要最优性能团队协作统一规范避免BigDecimal的误用系统复杂度降低系统复杂度和维护成本如何选择场景推荐方案理由简单业务系统long性能好简单可靠银行金融系统BigDecimal精度要求极高高并发交易long性能至关重要复杂税务计算BigDecimal需要复杂小数运算新项目启动long技术债务少技术选型从来都不是单一的理解业务的需求能选择出最适合的技术方案就是最好的方案。

更多文章