BigDecimal和Long表示金额哪个更合适,怎么选择?
金额存储:Long 分还是 BigDecimal 元?
面试时直接抛结论:能用 Long 分解决的场景就别上 BigDecimal,但一涉及乘除和小数精度,BigDecimal 就是刚需。 下面我把两种方案的优劣、选型逻辑和落地经验一次性说透。
🪙 方案一:Long 存分(整数方案)¶
做法:把金额统一转换成最小货币单位(人民币是分),用 long 或 Long 存储。比如 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。
⚖️ 如何选择?看业务操作的“算术密度”¶
我一般按下面这棵决策树来:
- 业务中是否涉及乘除运算?
- 如果只是加减(充值、扣款、退款、转账),完全不需要乘除 → 用
Long分 -
如果涉及乘除(打折、佣金分成、汇率转换、分期计算利息) → 必须用
BigDecimal,哪怕只在一小部分场景用到 -
精度是否要求到分以下?
- 如果业务需要厘、毫、甚至四位小数(比如基金净值) →
BigDecimal -
否则可以
Long -
性能是否敏感?
- 高并发下的秒杀、支付流水表,每秒几万笔写入 →
Long优先(数据库索引更快,网络开销小) - 运营报表、财务对账,允许稍慢的查询 →
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分