购物车里两件商品,一件 0.1 元,一件 0.2 元,合计显示 0.30000000000000004。用户截图发过来,问是不是在坑他。
这是二进制浮点的老问题。float 和 double 用二进制存小数,0.1 在二进制里是无限循环,存进去就已经近似了,加完误差被放大。
decimal 为什么能算准
decimal 用十进制存储,内部是整数加一个缩放因子,0.1 就存成 1 乘 10 的负一次方,不丢精度。C# 的 decimal 是 128 位,28 到 29 位有效数字,算钱够用。
代价是慢。decimal 的运算比 double 慢一个数量级,也占内存。所以只在需要精确的地方用,物理模拟、图形计算该用 double 还是 double。
各语言怎么写
- C#:decimal 关键字,字面量加 m 后缀。
- Java:BigDecimal,构造要传字符串,传 double 会把误差带进去。
- Python:decimal.Decimal,同样传字符串。
- MySQL:DECIMAL(10,2),存金额够用,别用 FLOAT。
- JavaScript:没有内置类型,只能放大成整数算,或者引入 decimal.js。
几个实际坑
Java 的 BigDecimal 做除法必须给精度和舍入模式,除不尽会直接抛异常。金额统一用 HALF_UP,别用默认的 HALF_EVEN,对账时差一分钱很难查。
数据库存了 DECIMAL,读出来又转成 double,等于白搭。ORM 映射时确认字段类型,别让它默认走浮点。
比较大小用 compareTo,不要用 equals。BigDecimal 的 equals 连小数位数一起比,1.0 和 1.00 不相等。
前端展示也得管。后端算完是 0.30,接口传成 number 到了前端又变回浮点,格式化时统一保留两位更稳。
汇率换算这类乘除,用 decimal 也要先定精度。四舍五入的位置不同,两边算出来的数就对不上。
规则很简单:钱、税率、积分,一律用 decimal 或者整数分。一旦用浮点,误差迟早会在某张对账单上冒出来。