Python查询MySQL时float字段出现舍入错误的原因及解决方法
解决MySQL Float类型与Python交互的精度问题
在使用mysql-connector-python查询MySQL的companyProperties表时,遇到浮点类型字段精度丢失问题:表中ourCostPerSegment和theirCostPerSegment字段为float类型,存储值分别为0.0069和0.02,但Python查询返回结果为0.006899999920278788和0.019999999552965164,导致计算误差过大。
相关信息
表结构
DESCRIBE companyProperties; +---------------------+------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +---------------------+------------+------+-----+---------+-------+ | dateEffective | date | NO | | NULL | | | isAgeGated | tinyint(1) | NO | | 0 | | | ourCostPerSegment | float | NO | | NULL | | | theirCostPerSegment | float | NO | | NULL | | | monthlyBaseFee | int | NO | | NULL | | | friendlyName | text | NO | | NULL | | +---------------------+------------+------+-----+---------+-------+
表中数据
SELECT * FROM companyProperties; +---------------+------------+-------------------+---------------------+----------------+--------------+ | dateEffective | isAgeGated | ourCostPerSegment | theirCostPerSegment | monthlyBaseFee | friendlyName | +---------------+------------+-------------------+---------------------+----------------+--------------+ | 2023-08-03 | 1 | 0.0069 | 0.02 | 250 | test | +---------------+------------+-------------------+---------------------+----------------+--------------+
查询代码(FastAPI环境)
Python版本:
python3 --version Python 3.10.12
查询逻辑:
conn = db.cursor(dictionary=True, prepared=True) getCompanyProperties = "SELECT * FROM `{}`.companyProperties ORDER BY dateEffective DESC LIMIT 1".format(companyKey["companyKey"]) conn.execute(getCompanyProperties, []) companyPropertiesObject = conn.fetchall() print(companyPropertiesObject)
查询返回结果
[{'dateEffective': datetime.date(2023, 8, 3), 'isAgeGated': 1, 'ourCostPerSegment': 0.006899999920278788, 'theirCostPerSegment': 0.019999999552965164, 'monthlyBaseFee': 250, 'friendlyName': 'test'}]
服务器信息
SHOW VARIABLES LIKE "%version%"; +--------------------------+-------------------------+ | Variable_name | Value | +--------------------------+-------------------------+ | admin_tls_version | TLSv1.2,TLSv1.3 | | immediate_server_version | 999999 | | innodb_version | 8.0.33 | | original_server_version | 999999 | | protocol_version | 10 | | replica_type_conversions | | | slave_type_conversions | | | tls_version | TLSv1.2,TLSv1.3 | | version | 8.0.33-0ubuntu0.22.04.2 | | version_comment | (Ubuntu) | | version_compile_machine | x86_64 | | version_compile_os | Linux | | version_compile_zlib | 1.2.13 | +--------------------------+-------------------------+
已尝试的方法:使用FLOAT(M,D)指定格式无效;将字段改为TEXT后在Python转float可行,但方案不够理想。
解决方案
1. 改用DECIMAL类型存储(推荐)
FLOAT是二进制浮点类型,无法精确存储所有十进制小数,这是问题根源。改用MySQL的DECIMAL类型(十进制浮点),可以完全匹配业务中的精确小数需求。
修改表结构的SQL:
ALTER TABLE companyProperties MODIFY COLUMN ourCostPerSegment DECIMAL(10,4) NOT NULL, MODIFY COLUMN theirCostPerSegment DECIMAL(10,4) NOT NULL;
DECIMAL(M,D)中,M是总位数,D是小数位数,根据业务需求调整(比如这里用10位总长度、4位小数,适配你的0.0069、0.02场景)。- 此方法从存储层面解决精度问题,后续查询无需额外处理,Python会直接拿到精确的数值。
2. 查询时在MySQL中格式化返回
如果无法修改表结构,可以在查询时用MySQL的ROUND()函数对浮点字段进行精确格式化:
SELECT dateEffective, isAgeGated, ROUND(ourCostPerSegment, 4) AS ourCostPerSegment, ROUND(theirCostPerSegment, 4) AS theirCostPerSegment, monthlyBaseFee, friendlyName FROM `{}`.companyProperties ORDER BY dateEffective DESC LIMIT 1
- 注意:
ROUND()返回的仍是数值类型,但如果需要更高精度的计算,还是推荐第一种方法。
3. Python端使用Decimal模块处理精度
在Python中利用decimal模块将查询到的浮点值转换为高精度十进制类型,避免计算误差:
from decimal import Decimal, getcontext # 设置精度,根据业务需求调整 getcontext().prec = 4 # 转换查询结果中的浮点字段 cost_data = companyPropertiesObject[0] cost_data['ourCostPerSegment'] = Decimal(str(cost_data['ourCostPerSegment'])) cost_data['theirCostPerSegment'] = Decimal(str(cost_data['theirCostPerSegment'])) # 后续计算使用Decimal类型,避免误差 total_cost = cost_data['ourCostPerSegment'] * 10000
- 转换时要先转成字符串再传入
Decimal,避免直接用浮点值初始化(因为浮点值本身已经是不精确的)。
内容的提问来源于stack exchange,提问作者Weston Simon
相关产品推荐
相关产品推荐

