定点类型与浮点误区
精确小数用 DECIMAL(p, s)(PostgreSQL 里是 numeric):p 是总位数,s 是小数位数,金额、税率、库存数量这类要求精确的值一律用它。FLOAT 与 DOUBLE 是二进制近似类型,0.1 这类十进制小数根本无法精确表示,累加与等值比较都会出现偏差。类型要一次定对:整数部分位数不足会溢出,小数位数不足会被四舍五入,且中间计算还会按规则推导结果类型。ROUND 只改变显示精度,不改变底层存储。
基本信息
| 定点类型 | MySQL 用 DECIMAL(p,s),PostgreSQL 用 numeric(p,s) |
|---|---|
| 参数含义 | p 是总位数(含小数位),s 是小数位数 |
| 近似类型 | FLOAT 单精度与 DOUBLE 双精度存放的是近似值 |
| 典型误差 | 0.1 + 0.2 在浮点下不等于 0.3 |
| 等值比较 | 浮点值直接比较可能永远不成立 |
| 使用建议 | 金额、税率、数量、积分一律用定点类型 |
典型使用场景
- 金额存储:订单金额、税额、余额
- 税率与折扣计算:需要固定小数位与明确的舍入方式
- 库存与数量:参与加减后必须精确
- 对账场景:多笔累加后与账面完全一致
- 积分与虚拟币:不允许出现小数漂移
- 接口精度约定:与前后端约定保留几位小数
示例
示例
-- 建表时用定点类型
CREATE TABLE orders (
amount DECIMAL(12,2) NOT NULL DEFAULT 0.00, # 最多 10 位整数、2 位小数
tax_rate DECIMAL(5,4) NOT NULL DEFAULT 0.0000, # 如 0.0600
qty INT NOT NULL DEFAULT 0
) ENGINE=InnoDB;
-- 计算:先算再按要求舍入
SELECT amount,
amount * tax_rate AS tax_raw,
ROUND(amount * tax_rate, 2) AS tax,
ROUND(amount * (1 + tax_rate), 2) AS payable
FROM orders;
-- 浮点误差演示(仅说明问题,不要这样存金额)
SELECT 0.1 + 0.2; -- 近似值,不精确等于 0.3类型选择参考
| 类型 | 特点 | 适用 |
|---|---|---|
| DECIMAL / numeric | 精确十进制,可指定位数 | 金额、税率、数量 |
| INT / BIGINT | 精确整数 | 计数、积分、以分为单位的金额 |
| FLOAT | 近似,单精度 | 科学计算、允许误差的场景 |
| DOUBLE | 近似,双精度 | 统计估算、允许误差的场景 |
| CHAR / VARCHAR | 文本 | 绝不要用来存数字参与计算 |
另一种常见做法是用整数存最小货币单位(如以分为单位存金额),配合应用层换算,同样能避免浮点误差,且比较与索引都简单。
用 FLOAT 或 DOUBLE 存金额,累加几十万行后偏差肉眼可见,对账会出现无法解释的几分钱差额。
ROUND 的舍入规则在两库都是四舍五入到指定位数,但它只影响结果值的精度,不改变列的存储类型;需要固定小数位就靠列类型约束。
使用要点
- 选类型时留够整数位:业务增长后金额位数会变多
- 明确小数位数:税率用四位、金额用两位是常见约定
- 乘法结果要显式舍入,避免下游拿到超长小数
- 涉及金额的比较用定点类型或整数分单位,不用浮点
- 报表里统一展示精度,避免同一指标在不同页面位数不同
- 导出的浮点数据要带精度说明,下游解析时按约定处理