SQL 中如何处理浮点数精度丢失的 DECIMAL / NUMERIC 替代

11次阅读

DECIMAL 和 NUMERIC 能避免浮点数精度丢失,因为它们是定点数类型,以整数形式存储“整数部分 + 小数位数”,不依赖 IEEE 754 浮点表示,全程无舍入误差。

SQL 中如何处理浮点数精度丢失的 DECIMAL / NUMERIC 替代

为什么 DECIMALNUMERIC 能避免浮点数精度丢失

因为它们是定点数类型,底层以整数形式存储「整数部分 + 小数位数」,不依赖二进制浮点表示。比如 DECIMAL(10,2) 表示最多 10 位数字、其中小数占 2 位,值 1234567.89 会被精确存为整数 123456789 再除以 100,全程无舍入误差。

FLOATREAL 类型按 IEEE 754 存储,像 0.1 + 0.2 在数据库里可能算出 0.30000000000000004 —— 这在 金融、计费、库存扣减等场景不可接受。

DECIMAL 声明时的两个参数怎么选

声明形如 DECIMAL(M,D)M 是总位数(精度),D 是小数位数(标度)。选错会导致截断或报错:

  • M 必须 ≥ D,否则建表失败(如 DECIMAL(2,5) 不合法)
  • 插入超长整数(如向 DECIMAL(5,2) 插入 1234.567)会四舍五入成 1234.57;但插入 12345.67 会直接报错,因整数部分已超 5−2=3
  • 金融常用 DECIMAL(19,4)(覆盖万亿级金额 + 四位小数,适配大多数会计系统);积分、优惠券面额可用 DECIMAL(12,2)

FLOAT 迁移数据到 DECIMAL 的坑

直接 ALTER TABLE …… MODIFY COLUMN 可能静默截断或四舍五入,尤其当原字段含大量非精确值(如 0.1 实际存为 0.10000000149011612):

  • 先用 SELECT column_name, CAST(column_name AS DECIMAL(12,2)) FROM table 预览转换效果
  • 避免用 ROUND() 二次处理,它可能掩盖原始误差;应优先重算源头数据
  • MySQL 8.0+ 支持 ALTER TABLE …… ALTER COLUMN …… SET DEFAULT,但默认值也需显式写成字符串或 DECIMAL 字面量(如 DEFAULT 0.00 不安全,改用 DEFAULT '0.00'

应用层读写 DECIMAL 时的常见疏漏

ORM 或驱动常把 DECIMAL 当作字符串或浮点数返回,导致二次精度丢失:

  • Python 的 pymysql 默认将 DECIMAL 返回为 decimal.Decimal,但若配置了 cursorclass=pymysql.cursors.DictCursor 且未启用 decimal 解析,可能退化为 float
  • Java JDBC 中需确认 ResultSet.getBigDecimal() 被调用,而非 getDouble();Spring Boot 的 @Column(precision = 19, scale = 4) 仅影响 DDL,不强制运行时类型
  • JSON 序列化时,decimal.Decimal 默认无法直出,需自定义 encoder 转为字符串,否则经 float 中转就废了

真正关键的不是“换类型”,而是确保从存储、传输到展示,整条链路都拒绝浮点数介入。哪怕数据库用了 DECIMAL,只要中间某一层用了 parseFloat()float64 解析,精度就归零了。

text=ZqhQzanResources