跳转至

BigDecimal和Long表示金额哪个更合适,怎么选择?

金额存储:Long 分还是 BigDecimal 元?

面试时直接抛结论:能用 Long 分解决的场景就别上 BigDecimal,但一涉及乘除和小数精度,BigDecimal 就是刚需。 下面我把两种方案的优劣、选型逻辑和落地经验一次性说透。


🪙 方案一:Long 存分(整数方案)

做法:把金额统一转换成最小货币单位(人民币是分),用 longLong 存储。比如 12.34 元 → 1234 分。

优点:

  • 没有精度问题:整数运算完全在二进制中精确表示,不会出现 0.1 + 0.2 = 0.30000000000000004 这种浮点误差。

  • 性能高:整数 + - 直接走 CPU 指令,比 BigDecimal 对象的方法调用快 1~2 个数量级。数据库里 BIGINT 索引效率也高于 DECIMAL

  • 序列化小:JSON 传输一个数字比字符串更省流量。

缺点:

  • 只能表达分精度:如果业务需要厘、毫或更小单位(比如利息计算、大宗商品单价),Long 分就存不下。

  • 转换别扭:前端展示要 /100,后端接收要 *100,容易出错(比如直接 long money = (long)(doubleVal * 100) 会丢失精度,必须用 BigDecimal 中转)。

  • 乘除运算必须换 BigDecimal:打折、汇率换算、分摊等,一用 long 做乘除就会丢失小数部分(比如 1234分 × 0.68 = 839.12分,取整就少算或向零舍入),必须引入 BigDecimal 保证精确。


🧮 方案二:BigDecimal 存元(精确小数)

做法:用 new BigDecimal("12.34")BigDecimal.valueOf(12.34) 构造对象,任意精度,支持舍入模式。

优点:

  • 任意精度:支持任意小数位,适合金融、利率、外汇等对精度有极致要求的场景。

  • 舍入规则可控:setScale(2, RoundingMode.HALF_UP) 明确指定四舍五入,符合会计规范。

  • 直接表达业务语义:跟用户看到的数字一致,不易出错。

缺点:

  • 性能开销大:对象分配 + 方法调用,运算比 long 慢 10~50 倍,大量并发下 GC 压力也大。

  • 代码繁琐:不能直接用 + - * /,必须调方法,写起来啰嗦。

  • 序列化坑:JSON 序列化默认会变成数字(可能丢失精度)或字符串(传输膨胀),需要定制。

  • 数据库映射:对应 DECIMAL 类型,索引效率不及 BIGINT


⚖️ 如何选择?看业务操作的“算术密度”

我一般按下面这棵决策树来:

  1. 业务中是否涉及乘除运算?
  2. 如果只是加减(充值、扣款、退款、转账),完全不需要乘除 → 用 Long
  3. 如果涉及乘除(打折、佣金分成、汇率转换、分期计算利息) → 必须用 BigDecimal,哪怕只在一小部分场景用到

  4. 精度是否要求到分以下?

  5. 如果业务需要厘、毫、甚至四位小数(比如基金净值) → BigDecimal
  6. 否则可以 Long

  7. 性能是否敏感?

  8. 高并发下的秒杀、支付流水表,每秒几万笔写入 → Long 优先(数据库索引更快,网络开销小)
  9. 运营报表、财务对账,允许稍慢的查询 → BigDecimal 也 OK

🔧 最佳实践:混合使用,各取所长

实际项目里我常常这么干:

  • 数据库存储和内部流转:统一用 BIGINT 存分。所有接口、消息队列里的金额字段都用 long,避免歧义。

  • 业务计算层:遇到乘除或需要小数精度时,临时转为 BigDecimal 运算,算完再 multiply(BigDecimal.valueOf(100)).longValue() 转回分。

  • 显示层:前端交互之前,在 DTO 里转为“元”字符串或 BigDecimal,避免前端自己处理精度。

  • 重要操作加校验:比如扣库存时,用 long newStock = oldStock - quantity; 再判断 newStock >= 0,简洁又安全。

示例代码片段(计算折扣价):

long priceInCents = 1234; // 12.34元
double discount = 0.68;
// 转 BigDecimal 计算
BigDecimal price = BigDecimal.valueOf(priceInCents, 2); // 12.34
BigDecimal discounted = price.multiply(BigDecimal.valueOf(discount));
long finalPriceInCents = discounted.setScale(0, RoundingMode.HALF_UP)
                                  .multiply(BigDecimal.valueOf(100))
                                  .longValue(); // 839分