专业服务 · 烟雨平生
Python笔记建站笔记资讯Docker运维
公告烟雨平生 —— 内容持续更新,欢迎指正。
首页 / Python笔记 / Flask SQLAlchemy 浮点数类型Float 丢失精度 解决方法 | 烟雨平生

Flask SQLAlchemy 浮点数类型Float 丢失精度 解决方法 | 烟雨平生

Python笔记 2026年10月02日 14:15 1822 字

然后,当我把一个 2 位小数的数存进数据库,一查看,有的变成了 1 位小数,有的直接无小数了,反正就奇奇怪怪的。这就是因为精度损失了。我们一般在操作数据库的时候,可能用 decimal 或者 double 这样的字段类型来替换 float 类型,就可以解决这个问题了。但是在 SQLAlchemy 里,事情没有这么简单:模型字段、数据库方言、Python 数值类型和序列化层都会影响最终结果,单换一个类型名并不一定万事大吉。

先理解 Float 为什么不可靠

Float 属于近似数值类型,底层通常遵循 IEEE 754 浮点表示。很多十进制小数,比如常见的分位、角位,在二进制里无法被精确表示,只能截断或舍入。数据库里的 FLOAT、REAL、DOUBLE 都有类似特性,只是有效位数不同。所以存进去是两位小数,读出来变成一位或没有小数,并不一定是 SQLAlchemy 的 bug,而是浮点类型本身的设计目标造成的。

严格说,double 只是比 float 有更多有效位,仍然不能保证固定小数位。金额、单价、税率、账目这类需要精确十进制的场景,不应该依赖 float 或 double。

优先使用 Numeric 或 DECIMAL

在 Flask-SQLAlchemy 模型中,可以把 Float 换成 Numeric 或 DECIMAL:

“`python

from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()

class Product(db.Model):

id = db.Column(db.Integer, primary_key=True)

price = db.Column(db.Numeric(10, 2), nullable=False)

“`

这里的 `Numeric(10, 2)` 表示总位数和小数位数,具体精度要按业务确定。SQLAlchemy 的 Numeric 默认 `asdecimal=True`,读取时返回 `decimal.Decimal`。在 MySQL、PostgreSQL 等数据库中,它通常会映射为 DECIMAL 或 NUMERIC 类型,能按定义保留小数位。SQLite 的类型系统比较宽松,测试时仍要覆盖写入和读取路径。

不要误以为 `Float(precision=2)` 就能固定两位小数。Float 的 precision 更多影响 DDL 或数据库方言行为,并不保证值在计算和存储中精确。

Python 侧也要用 Decimal

只改数据库字段还不够。如果写入前先用 float 运算,再传给 Numeric,精度在进入数据库之前就已经丢了。推荐从字符串或 Decimal 构造:

“`python

from decimal import Decimal

price = Decimal(“12.34”)

“`

避免 `Decimal(12.34)`,因为参数会先变成 float。计算、累加、比较时也尽量保持 Decimal,不要中途转回 float。对于需要固定小数位的业务,可以在序列化或展示前再统一格式化。

迁移与已有数据

如果表里原本就是 Float,改成 Numeric 需要迁移。可以用 Alembic 生成变更,但要评估已有数据的转换方式。比较稳妥的做法是新增一个 Numeric 列,回填数据,切换代码读写新列,确认无误后再删除旧列。回填时,如果原始来源是字符串或可追溯的精确值,优先用字符串转换;如果只能从 Float 列转换,那只能得到近似值,因为旧数据已经损失了精度。

序列化与接口输出

Flask 返回 JSON 时,Decimal 不能总是被默认编码器直接处理。可以转成字符串保留精确值,也可以按展示需要转成 float 并格式化。如果接口调用方要继续做金额计算,建议输出字符串并说明精度,避免对方再落入浮点陷阱。

小结

Float 适合科学计算、传感器数据等允许近似值的场景;金额、单价、税务、账目等需要固定小数位的业务,应使用 Numeric 或 DECIMAL,并让 Python 侧使用 Decimal。SQLAlchemy 中把 Float 换成 Numeric 是第一步,表单校验、计算逻辑、数据迁移和 JSON 序列化一起调整,才能真正解决浮点数精度丢失的问题。