应先将原始值转为decimal再聚合,而非对聚合结果用round();因float/double存储存在二进制误差,导致avg()计算失准,round()无法修复精度链断裂。

直接结论:别对聚合结果用 ROUND(),而要对原始值做 DECIMAL 转换后再聚合,最后再 ROUND 输出。
ROUND(AVG(x), 2) 为什么常出错
这不是函数写错了,而是精度链断在了第一步:AVG() 输入如果是 FLOAT 或 DOUBLE,计算过程已引入二进制误差。比如原始值是 85.75,但底层存的是 85.74999999999999,AVG() 算出来就是略低的数,再 ROUND(, 2) 可能仍得 85.74。
- SQL Server 和 PostgreSQL 对 .5 的舍入规则不同(银行家 vs 算术),同一语句跨库结果可能不一致
- MySQL 的 ROUND() 虽默认算术舍入,但若 x 是 FLOAT 类型,ROUND(1.235, 2) 有时返回 1.23 —— 因为 1.235 实际存储为 1.234999999…
- ROUND(AVG(x), 2) 返回 DECIMAL(10,3) 类型时,显示为
85.750,不是 bug,是类型继承;要去掉尾随零必须显式CAST(... AS DECIMAL(10,2))
正确写法:从源头控制精度链
关键不是“怎么 ROUND”,而是“ROUND 谁”。聚合前把浮点字段转成 DECIMAL,让整个计算链都在定点数上跑。
- 安全写法:
ROUND(CAST(SUM(score) AS DECIMAL(18,6)) / COUNT(*), 2)—— 先 SUM 转定点,再除,再舍入 - 更简洁等价写法(PostgreSQL):
ROUND(AVG(score::DECIMAL(18,6)), 2) - MySQL 推荐:
ROUND(AVG(CAST(score AS DECIMAL(18,6))), 2) - 注意 DECIMAL 参数留余量:
DECIMAL(20,2)比DECIMAL(10,2)更稳妥,避免 SUM 后整数位溢出
GROUP BY 中用 ROUND(price, 2) 分组会漏数据
这根本不是“按两位小数分组”,而是把所有四舍五入后相等的 price 强行合并。19.994999999 和 19.995000000 都变成 19.99,但业务上它们可能属于不同价格带。
- 想按区间统计,用
CASE WHEN price BETWEEN 0 AND 99.99 THEN '0-99.99' END更可靠 - 必须数值分段时,
FLOOR(price * 100) / 100.0(截断)比ROUND(price, 2)稳定,避开 0.5 边界抖动 - SQL Server 不支持直接
GROUP BY ROUND(price, 2),得先在 SELECT 中定义别名,再 GROUP BY 别名,否则报Invalid column name
WHERE 条件里千万别用 ROUND() 做等值匹配
WHERE ROUND(price, 2) = 19.99 看似合理,实则绕过索引、性能差,且逻辑危险:它会把 19.985~19.994999… 全扫进来,和业务预期不符。
- 正确做法是用容差:
WHERE ABS(price - 19.99) - 或更稳妥:
WHERE price >= 19.985 AND price - 如果 price 字段本身就是 FLOAT,连
WHERE price = 19.99都几乎必然失败 —— 存进去的就不是精确 19.99
最易被忽略的一点:ROUND 是输出层动作,不是修复手段。一旦你用了 FLOAT 存金额、没在导入时 CAST 成 DECIMAL、又在 GROUP BY 里套 ROUND 表达式,那后续所有 ROUND 都只是在掩盖漂移,而不是纠正它。










