用bigdecimal处理电商金额的核心是“可控”:避免double构造陷阱,统一用字符串或整数创建;所有运算显式指定scale和roundingmode;推荐以“分”为单位整数运算;判等用compareto而非equals。

用 BigDecimal 替代 float 或 double 处理电商金额,核心不是“完美”,而是“可控”——关键在于避免构造陷阱、统一缩放规则、明确舍入逻辑。
别用 double 构造 BigDecimal
这是最常见也最致命的错误。直接传 double 值会把浮点误差一起带进来:
BigDecimal price = new BigDecimal(19.99); // 实际值可能是 19.989999999999998...
始终用字符串或整数构造:
-
new BigDecimal("19.99")—— 最常用,推荐 -
BigDecimal.valueOf(1999).divide(BigDecimal.TEN.pow(2))—— 适合从分单位(整数)转换 -
BigDecimal.valueOf(19.99)—— 底层仍用字符串解析,安全(但语义不如字符串清晰)
所有运算必须显式指定 scale 和 RoundingMode
加减法可保留精度,但乘除必须明确结果的小数位数和舍入方式,否则抛异常或行为不可控:
- 加减:建议统一用
add(other).setScale(2, RoundingMode.HALF_UP)显式对齐 - 乘法:
price.multiply(quantity, new MathContext(2, RoundingMode.HALF_UP))或更清晰的multiply(other).setScale(2, RoundingMode.HALF_UP) - 除法(如分摊、折扣率):
total.divide(count, 2, RoundingMode.HALF_UP)—— 必须指定 scale 和 mode
电商场景中 RoundingMode.HALF_UP 是主流(四舍五入),但需与财务规范对齐,有些场景要求 HALF_EVEN(银行家舍入)。
统一用“分”为单位做整数运算(可选但推荐)
彻底规避小数处理,适合高并发、强一致性系统:
- 数据库字段存
INT类型,单位为“分”(如 1999 表示 19.99 元) - Java 层全程用
long或BigInteger计算,仅在展示层转回元:amountInCents / 100.0→ 不用于计算;应使用BigDecimal.valueOf(amountInCents).divide(BigDecimal.HUNDRED, 2, RoundingMode.HALF_UP) - 优势:无舍入争议、序列化安全、比较快;缺点:开发习惯需调整,API 层需做好单位转换
equals() 和 compareTo() 别混用
BigDecimal.equals() 比较值 + scale(1.0 ≠ 1.00),而金额比大小、判相等应只看数值:
- 判相等用
compareTo(BigDecimal.ZERO) == 0或compareTo(other) == 0 - 排序、去重、Map key 等场景,优先用
compareTo逻辑,或先stripTrailingZeros()再 equals - 入库前建议调用
stripTrailingZeros().toPlainString()存储,避免 "1.00" 和 "1.0" 被当成不同值











