SQLite 整数除法:算完再转小数,为什么已经丢掉了半个

10-01 3阅读

一张完成率表把一半算成了零

统计任务完成率时,完成数是一,计划数是二。查询能运行,得到的比率却是零,给结果乘一百之后仍然是零。问题通常出在两项都是整数,除法已经执行了整数运算;显示层多保留几位小数,只会把零写得更长,不能恢复先前失去的部分。

SQLite 会根据操作数的实际类型选择数值运算方式。两项都是整数且运算没有溢出时,除法采用整数计算,结果向零截断。希望得到带小数的比例,应在除法之前把至少一项变成实数,而不是在整个除法外面最后再套一次转换。

把每个阶段的类型一起打印

下面用 Python 标准库连接内存 SQLite,保存为文件直接执行即可,不会改动现有数据库。本文在 Python 三点十二和 SQLite 三点五十三验证;涉及的表达式不依赖新版本特性。第一组查询把除前转换、除后转换和负数运算并排放置。

第二组是迷你任务表,包括普通比率、超过计划、计划数为零以及计划数未知。示例对每一行同时断言结果和 SQLite 返回的类型,避免只检查显示文字。已知的一半应是实数,无法计算的比率则保留为空值,让调用方另作处理。

SQLite 整数除法:算完再转小数,为什么已经丢掉了半个

AI概念配图,非真实界面

import sqlite3

with sqlite3.connect(':memory:') as db:
    raw = db.execute("""
        SELECT 3/2, CAST(3/2 AS REAL), CAST(3 AS REAL)/2,
               -3/2, 3/2.0, typeof(3/2), typeof(3/2.0)
    """).fetchone()
    assert raw == (1, 1.0, 1.5, -1, 1.5, 'integer', 'real')
    print('division:', raw)
    db.execute('CREATE TABLE tasks(name TEXT, done INTEGER, planned INTEGER)')
    db.executemany('INSERT INTO tasks VALUES (?,?,?)',
                   [('half', 1, 2), ('over', 3, 2),
                    ('zero', 1, 0), ('unknown', 1, None)])
    rows = db.execute("""
        WITH rates AS (
            SELECT name, CAST(done AS REAL) / NULLIF(planned, 0) AS ratio
            FROM tasks
        )
        SELECT name, ratio, typeof(ratio) FROM rates ORDER BY name
    """).fetchall()
    expected = [('half', 0.5, 'real'), ('over', 1.5, 'real'),
                ('unknown', None, 'null'), ('zero', None, 'null')]
    assert rows == expected
    for row in rows:
        print(row)
    assert db.execute('SELECT 1/0, 1/NULL').fetchone() == (None, None)
print('division and missing-ratio checks passed')

先决定运算,再决定展示

三除以二得到整数一,把这个一转成实数只会得到一点零;先把三转成实数再除以二,才会得到一点五。这里转换的位置影响计算含义,不是格式风格。写复杂表达式时,建议给每个中间步骤起别名,先核对数值和类型,再做最终百分比显示。

向零截断也意味着负三除以二等于负一,而不是向负无穷舍入得到的负二。若算法需要数学意义上的向下取整,必须明确实现那项规则。不要从正数样本推断负数行为,尤其是用差额、偏移或温度变化参与比例计算的时候。

写成一乘一点零再相除也能触发实数运算,不过显式 CAST 通常更容易让读者看出意图。声明列名为某种带小数意味的类型,不一定保证每个值都以实数存储;排错时应查看表达式的 typeof 结果和实际输入,不能只凭建表语句判断。

零分母与未知分母需要保留含义

SQLite 对除以零返回 NULL。示例仍用 NULLIF 把零分母显式转换为空值,是为了把业务边界写在查询里,阅读和迁移时更清楚。计划数本来就是 NULL 时,比率同样未知;查询得到相同结果,并不表示这两种原因在业务上也完全一样。

若报表必须区分“没有计划”和“计划为零”,可以另外返回状态列,保留原始分母供核对。直接把所有 NULL 用零替换,会把无法计算说成完成率零;若下游又拿这些零求平均,整体结论还会改变。展示文字与统计规则最好分别定义。

同样需要先明确汇总口径。把每行比率平均,与把完成总数除以计划总数,通常不是一回事;它们给不同大小的任务分配了不同权重。修复整数除法只是保证选定公式按预期计算,并不能替你选对公式,验收样本应包含规模相差较大的记录。

最后,REAL 是二进制浮点数,解决了整数截断,并不等于精确十进制计算。需要严格最小单位的业务可先设计整数单位与舍入位置,或者使用适当的十进制计算方案。保留分子分母、检查零值、说明展示精度,才能让这个比率以后仍然解释得清楚。

参考资料

文章版权声明:除非注明,否则均为云鹊BLOG原创文章,转载或复制请以超链接形式并注明出处。