浮点数分组、聚合和比较必须避免直接使用ROUND或等号,应先CAST为DECIMAL再运算;推荐FLOOR截断或CASE区间分组,WHERE条件须用CAST或ABS容差。

GROUP BY 分组键不能依赖浮点计算
直接用 ROUND(price, 2) 或 price * 1.1 当分组依据,会导致语义相同但二进制表示略有差异的值被拆成不同组。比如 19.995 和 19.994999999999999 在 IEEE 754 下可能被判定为两个键,本该合并的行被分开统计。
- 别写
GROUP BY ROUND(price, 2)—— 这不是“按两位小数分组”,而是把所有四舍五入后相同的值强行归并,业务逻辑可能错乱 - 想按价格区间聚合,用
CASE WHEN price BETWEEN 0 AND 99.99 THEN '0-99.99' END更可靠 - 若必须数值分段,
FLOOR(price * 100) / 100.0(截断)比ROUND()稳定,避免 0.5 边界抖动 - SQL Server 不支持在
GROUP BY中直接写表达式,得先在SELECT中定义别名,再引用
SUM() 或 AVG() 前必须 CAST 到 DECIMAL
ROUND(SUM(price), 2) 是无效补救:SUM 已用浮点完成,误差固化;而 SUM(CAST(price AS DECIMAL(10,2))) 才是正确路径——每行先转定点数,再累加,全程避开浮点运算链。
- 已有 FLOAT 字段,查询中必须显式前置转换:
SUM(CAST(price AS DECIMAL(10,2))),不能等聚合完再ROUND() - DECIMAL 参数要留余量:
DECIMAL(18,2)适合金额,但 SUM 后整数位可能超限,建议用DECIMAL(20,2) - MySQL 严格模式下,CAST 溢出会报错;非严格模式静默截断,务必确认
sql_mode配置 - 窗口函数同理:
SUM(CAST(price AS DECIMAL(15,2))) OVER (PARTITION BY category),不能在OVER外套ROUND()
WHERE 条件匹配浮点字段永远别用 =
WHERE price = 19.99 几乎必然失败,因为存进去的就不是精确的 19.99,而是类似 19.990000000000002 或 19.989999999999998 的近似值。
- 安全写法一(推荐):
WHERE CAST(price AS DECIMAL(10,2)) = 19.99 - 安全写法二(容忍误差):
WHERE ABS(price - 19.99) - 别写
ROUND(price, 2) = 19.99—— 先查清你数据库里ROUND()返回类型,PostgreSQL 返回 NUMERIC,MySQL 返回 DOUBLE,SQLite 可能返回 INTEGER - ORM 层如果把 DECIMAL 字段当成字符串或 double 绑定,会触发隐式转换,精度照样丢
建表时没用 DECIMAL,现在补救还来得及吗?
来得及,但必须两步走:先改字段类型,再清洗存量数据。只改类型不清洗,旧浮点值仍带着误差。
- MySQL 示例:
ALTER TABLE orders MODIFY price DECIMAL(10,2); UPDATE orders SET price = ROUND(price, 2); - PostgreSQL 示例:
ALTER TABLE orders ALTER COLUMN price TYPE DECIMAL(10,2) USING ROUND(price, 2)::DECIMAL(10,2); - 迁移前务必测试:用
CAST(price AS CHAR)查看原始存储值,确认误差是否已消除 - 临时无法改表?所有查询都得带
CAST,包括 JOIN 条件、子查询、CTE 中的列
真正容易被忽略的是:应用层拿到 DECIMAL 字段后,如果用 float 类型反序列化,或者 ORM 默认映射为 double,整个链路又回到浮点陷阱里。精度控制必须贯穿存储、计算、传输、展示全链路。

















