在关系型数据库里,float属于近似数值类型,底层以二进制科学计数法存储,而DECIMAL是精确数值类型,按十进制标度定点保存。当我们使用CAST(float AS DECIMAL)时,数据库必须先读取float代表的近似十进制值,再按照目标DECIMAL的精度与标度进行舍入。这个过程中,精度参数(即DECIMAL(total, scale)中的total和scale)会直接改变舍入的位数与策略,从而导致看似相同的float在不同写法下得出不同的结果。

一、float的存储误差如何影响转换起点
很多开发者以为float变量里写的3.145就是准确的3.145,但实际上在IEEE 754单精度浮点中,3.145最接近的二进制值是约3.1450000953674316。当你把这个值存入float列再读取时,数据库拿到的真实数值已经带有微小偏差。这个偏差在CAST到DECIMAL时,会成为舍入计算的输入源。
例如下面这段SQL,我们先向float类型字段写入数据,再分别转成不同标度的DECIMAL:
CREATE TABLE test_float (f float); INSERT INTO test_float VALUES (3.145); SELECT CAST(f AS DECIMAL(10,2)) AS d2, CAST(f AS DECIMAL(10,1)) AS d1, CAST(f AS DECIMAL(10,0)) AS d0 FROM test_float;
在SQL Server中,上述查询通常返回d2为3.15、d1为3.1、d0为3。因为原始近似值略大于3.145,保留两位时第三位小数是5且后面有尾数,按四舍五入进一位;保留一位时看第二位小数4,舍去;保留整数时看第一位小数1,也舍去。如果原始值恰好是3.1449999,则结果会完全不同。
二、DECIMAL精度参数对舍入位的控制
DECIMAL(p, s)里的s表示小数点后的位数,也就是标度。CAST操作在得到float的真实十进制近似值后,只会针对这s位做舍入,多余的位数按数据库默认的舍入模式处理。不同s值相当于在不同的小数位上“切一刀”,自然会出现差异。
我们通过一个更直观的示例来看标度变化带来的分层舍入:
SELECT CAST(2.675 AS float) AS raw_float, CAST(CAST(2.675 AS float) AS DECIMAL(10,2)) AS to_2, CAST(CAST(2.675 AS float) AS DECIMAL(10,1)) AS to_1
由于2.675在float中实际约为2.674999952316284,转DECIMAL(10,2)时第三位近似为4,所以得到2.67而不是2.68;转DECIMAL(10,1)时看第二位小数7,得到2.7。这清楚地说明:精度不仅决定保留几位,还决定了从哪一位开始判断舍入,而判断所依赖的输入已经是浮点误差后的数。
三、不同数据库的舍入模式区别
除了float本身的误差,各数据库对“浮点转定点”的舍入规则也有差异。SQL Server与PostgreSQL一般遵循四舍五入(half up),但MySQL在较早版本中对FLOAT/DOUBLE转DECIMAL采用截断(truncate)而非四舍五入,直到特定版本后才统一为四舍五入。这就意味着同一句CAST语句,在不同库里结果可能不一致。
以下是在MySQL中观察行为的写法:
CREATE TABLE t (f float); INSERT INTO t VALUES (1.005); SELECT CAST(f AS DECIMAL(10,2)) FROM t;
若运行在采用截断逻辑的MySQL版本,1.005的float近似值约为1.00499999,CAST后得1.00;若版本改为四舍五入,同样近似值因不足1.005中间值仍得1.00,但若是1.015则可能得出1.01或1.02之分。因此在跨库迁移或写报表时,必须先用实际数据验证舍入表现。
四、避免精度差异导致业务错误的实践方案
如果业务涉及金额、费率等强一致性场景,最稳妥的做法是避免使用float存储待精确计算的值。建表时直接使用DECIMAL类型,从源头消除二进制误差。当必须从float接入数据时,可先转成精度更高的DECIMAL,再用ROUND函数显式控制舍入。
示例:先扩大标度承接浮点误差,再二次舍入:
SELECT CAST(f AS DECIMAL(20,10)) AS step1, ROUND(CAST(f AS DECIMAL(20,10)), 2) AS step2 FROM test_float;
这样做可以把float的误差暴露在高精度中间结果里,再由ROUND按明确规则收口。同时建议在应用层对关键数值做单元测试,覆盖边界值如x.005、x.015等,防止数据库版本升级后舍入行为变化引发资损。
五、总结对照表
为方便理解,我们把常见场景的差异整理如下:
| 原始float近似值 | CAST到DECIMAL(10,2) | CAST到DECIMAL(10,1) | 主要原因 |
|---|---|---|---|
| 3.14500009 | 3.15 | 3.1 | 标度不同,舍入位不同 |
| 2.67499995 | 2.67 | 2.7 | 浮点误差使千分位不足5 |
| 1.00499999 | 1.00 | 1.0 | 截断或四舍五入均舍去 |
通过上表可以看出,CAST(float AS DECIMAL)的结果是由“浮点真实值”和“目标标度”共同决定的。在写SQL时,应当把精度声明视为业务逻辑的一部分,而不是随意填写的数字。