根本原因是二进制浮点数无法精确表示十进制小数,导致0.1+0.2≠0.3;必须用decimal数据库类型、bigdecimal java类型、字符串初始化、显式精度控制及字符串序列化来全程保障金额精度。

财务计算中出现 0.1 + 0.2 = 0.30000000000000004 这类结果,根本原因不是代码写错了,而是用 double 或 float 存储和运算十进制金额时,二进制表示天然失真。这类误差在订单结算、分账、对账等环节会层层放大,必须从源头堵住。
数据库字段必须用 DECIMAL,不能用 FLOAT/DOUBLE
MySQL 中 FLOAT 和 DOUBLE 是近似数值类型,存储的是二进制浮点数,不适用于金钱。DECIMAL 才是定点数,按十进制精确存取。建表时建议:
- 金额字段用 DECIMAL(19,4):支持万亿级总额,保留 4 位小数(满足分、厘、毫、丝等扩展需求)
- 折扣、费率等比例字段用 DECIMAL(5,4)(如 0.9999)或 DECIMAL(3,2)(如 0.99)
- 避免在 SQL 中做
price * 0.95这类浮点运算,应传参计算后写入 DECIMAL 字段
Java 实体中必须用 BigDecimal,且初始化要严格
JPA 或 MyBatis 映射 DECIMAL 字段时,对应 Java 字段必须声明为 BigDecimal。关键细节:
- ❌ 禁止使用
new BigDecimal(0.1)—— double 参数已带误差,构造即失真 - ✅ 必须用
new BigDecimal("0.1")或BigDecimal.valueOf(0.1)(后者内部做了字符串转换) - getter/setter 不要自动转成 double 或 String 再处理,保持全程 BigDecimal 流
- 在实体中封装业务方法,例如:
getPayableAmount()内部调用price.multiply(discount).setScale(2, RoundingMode.HALF_UP)
运算过程必须显式控制精度和舍入
BigDecimal 的 add/subtract/multiply 默认保持精度,但 divide 会抛异常(除不尽),必须指定:
- 除法务必传入 scale(小数位数)和 RoundingMode,例如:
a.divide(b, 2, RoundingMode.HALF_UP) - 加减乘也建议统一 setScale,避免中间结果位数膨胀(如 19.99 × 3 得到 59.9700)
- 不要用 double 做中间变量,例如避免:
double tmp = bd.doubleValue(); ... new BigDecimal(tmp)
前端与序列化环节也要防丢失
JSON 序列化(如 Jackson)默认把 BigDecimal 转成 double 或科学计数法,导致前端再解析又出错:
- 配置 Jackson:注册
DecimalFormat或使用@JsonSerialize(using = ToStringSerializer.class),确保输出为字符串 "19.99" - 前端接收金额字段时,不要用 Number() 转,直接当字符串处理或用 BigInt + 单位换算(如分)
- 若用 Fastjson,需设置
SerializeConfig.globalInstance.put(BigDecimal.class, ToStringSerializer.instance)
不复杂但容易忽略
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











