根本原因是IEEE 754二进制浮点标准无法精确表示大多数十进制小数,导致FLOAT/DOUBLE类型在存储、计算中必然产生近似误差;DECIMAL通过定点十进制存储避免该问题,但需全程显式控制类型与精度。

SQL 查询结果中浮点数计算出现精度丢失,根本原因不是 SQL 引擎写错了,而是 FLOAT 和 DOUBLE 类型本身就不能精确表示大多数十进制小数——这是 IEEE 754 二进制浮点标准的固有缺陷,所有主流数据库(MySQL、PostgreSQL、SQL Server、Oracle)都逃不开。
为什么 0.1 + 0.2 ≠ 0.3?
因为 0.1 在二进制里是无限循环小数:0.0001100110011...(循环节 0011),而 FLOAT 或 DOUBLE 只能截取有限位存储(单精度 23 位尾数,双精度 52 位)。每次存储、读取、计算都在做近似,误差会累积:
-
SELECT 0.1 + 0.2;在 MySQL 中常返回0.30000000000000004 -
SELECT SUM(price) FROM orders;若price是FLOAT,百亿行求和后偏差可达数万元 - 用
WHERE price = 0.1查不到刚插入的0.1,因为存进去的其实是0.10000000149011612
隐式转换会让问题更隐蔽
你以为写了 DECIMAL 就安全了?不一定。只要链路中某处触发隐式转换,精度就可能当场崩塌:
- 调用存储过程时传
CALL calc(100, 0.08):0.08被 MySQL 当作DOUBLE解析,后续乘法结果变成DOUBLE - 表字段是
DECIMAL(10,2),但INSERT INTO t VALUES (100.0 / 3);中除法未显式转类型,中间结果可能降级为DOUBLE -
SELECT SUM(CAST(price AS DECIMAL(15,2)))看似稳妥,但如果原始price是VARCHAR,先转FLOAT再转DECIMAL,第一步就丢精度了
DECIMAL 不是“更高精度的浮点”,而是完全不同的体系
DECIMAL(p,s) 是定点十进制类型,它把数字按整数+小数两部分拆开存储(如 123.45 存成整数 12345 + 标度 2),全程不经过二进制小数转换。这意味着:
-
0.1就是精确的 1/10,不会变成近似值 -
DECIMAL(15,2)相加结果仍是DECIMAL,且精度可预测(两个DECIMAL(10,2)相加 →DECIMAL(11,2)) - 但必须显式声明精度和标度:
DECLARE total DECIMAL(15,2)合法,DECLARE total DECIMAL直接报错 - 超出定义范围时默认四舍五入(如
DECIMAL(5,2)存123.456→123.46),不是报错,容易被忽略
真正难的不是换类型,而是确保整条数据链——从入库、计算、传输到展示——没有一处偷偷把 DECIMAL 转成 FLOAT 或让字符串参与浮点运算。一个隐式转换,就能让前面所有高精度设计归零。

















