注册
达梦数据库FLOAT与NUMBER类型运算精度问题分析
专栏/技术分享/ 文章详情 /

达梦数据库FLOAT与NUMBER类型运算精度问题分析

DM_880930 2026/09/23 193 0 0
摘要

一、问题现象

  1. 测试 SQL 语句
-- 创建测试表tab1,c1为FLOAT二进制浮点类型,c2为NUMBER(4,2)十进制精确数值类型
create table tab1(c1 float,c2 number(4,2));
-- 插入测试数据 0.3、0.1
INSERT INTO tab1 VALUES(0.3,0.1);
-- 执行除法运算
SELECT c1/c2 FROM tab1;

理论数学计算结果:0.3 / 0.1 = 3。
实际数据库查询返回结果:2.9999999999999996,而非预期的 3,出现精度丢失问题。
image.png
补充现象:使用 disql 工具执行完全相同 SQL,输出结果显示为3.000000000000000E+00。这不是数据库内核计算结果改变,而是disql 客户端对浮点数输出做了格式化展示处理。

二、底层原理分析

  1. 两种数据类型本质区别
    FLOAT:二进制近似浮点类型 ;按照二进制存储小数,部分十进制小数无法二进制精确表达,0.3、0.1 这类十进制小数会被存储为近似值。
    存在固有浮点舍入误差,运算速度快,不适合财务、金额等高精度业务
    NUMBER(p,s):十进制精确数值类型;十进制编码存储,小数可以精确保存。
    无二进制浮点误差,适合金额、计量等要求精确计算的业务。

  2. 隐式类型转换规则
    达梦数据库在表达式运算时,会按照数据类型优先级,低优先级类型向高优先级类型做隐式转换:
    1. FLOAT(二进制 DOUBLE (53) 双精度浮点)优先级高于NUMBER;
    2. 表达式c1/c2中:c1 本身是 FLOAT;c2 是 NUMBER (4,2)。
    3. 运算前,NUMBER 类型的 c2 隐式转换为 DOUBLE (53) 二进制浮点数,两个浮点数做除法运算。
    4. 0.3、0.1 在二进制 FLOAT 存储中,本身就不是绝对精确,是无限二进制小数的截断近似值。
    5. 两个带有微小误差的二进制浮点数相除,最终得到2.9999999999999996近似结果,而不是精确的 3。

三、业务风险

1. 等值判断风险:where c1/c2 =3 条件会查询不到数据,因为实际值是2.9999999999999996,不等于 3;
2. 统计、财务计算:金额、计费、库存计量场景,浮点微小误差经过多次累加,误差会放大,造成业务金额错乱;

四、解决方案

方案 1:业务建表阶段,不使用 FLOAT,统一使用 NUMBER

create table tab_good(c1 number(4,2),c2 number(4,2));
insert into tab_good values(0.3,0.1);
select c1/c2 from tab_good;

全部为十进制精确数值,除法运算不会产生二进制浮点误差。
适用场景:财务、计费、统计、计量等绝大多数业务系统。FLOAT 仅适合科学计算、不需要严格精度的仿真类场景。
方案 2:存量表无法修改字段类型,运算时显式强制转换
存量表字段已经是 FLOAT,不能改表结构,在计算表达式中,把 FLOAT 字段显式 cast 转换为 NUMBER,强制使用十进制精确运算,避免隐式转 DOUBLE。
– 将float字段c1显式转为number再做除法

SELECT CAST(c1 AS NUMBER)/c2 FROM tab1;

优点:不需要改表结构,不需要依赖数据库参数NUMBER_MODE,新旧表都生效;
缺点:所有涉及 FLOAT 字段的计算表达式都需要手动增加转换,漏写就会出现精度问题。
方案 3:修改NUMBER_MODE 参数
执行系统过程修改参数:

SP_SET_PARA_VALUE(2, 'NUMBER_MODE', 1);

该参数作用:开启模式,将浮点运算自动转换为 NUMBER 精确数值。
对已经存在的旧表 tab1:继续插入新数据,执行c1/c2除法,依旧返回2.9999999999999996,精度问题没有修复;
新建测试表 tab3(同样 c1 float,c2 number),插入相同数据,执行相同除法,查询结果正确返回3。
image.png
image.png
关键结论:NUMBER_MODE=1参数只对修改参数之后新建的表生效,对已经创建完成的历史表无效。该参数是会话级 / 系统级行为控制,不会修改已经建好表的字段数据类型,也不会改写已有存储的数据;
注意:仅适用于新上线业务、新建表,存量历史表不受该参数影响,开启后整个实例运算行为改变,需要完整测试评估参数全局影响。
方案 4:存量表修改字段类型
如果业务允许 DDL 变更,把存量表 FLOAT 字段修改为 NUMBER 类型:

ALTER TABLE tab1 MODIFY c1 NUMBER;

注意:FLOAT 二进制存储的数据转换为 NUMBER 时,会把存储的近似值转成十进制,需要验证历史数据准确性;大数据量表 DDL 会产生锁,业务低峰期执行。

五、总结与开发规范

1. 业务系统优先选择 NUMBER,谨慎使用 FLOAT。FLOAT 是二进制近似存储,天生带有精度缺陷,不是数据库 bug,是浮点数存储机制本身特性;
2. 隐式类型转换是精度问题的主要来源,FLOAT 与 NUMBER 混合运算,NUMBER 会隐式转成双精度 DOUBLE,触发浮点误差;
3. NUMBER_MODE=1不能修复存量表,仅对新建表生效,不可作为存量问题的修复手段;
4. 开发规范:凡是涉及金额、数量、需要等值比较的字段,禁止使用 FLOAT,统一使用 NUMBER;当不可避免 FLOAT 和 NUMBER 混合运算,必须使用 CAST 显式转换,规避隐式转换带来精度风险。

评论
后发表回复

作者

文章

阅读量

获赞

扫一扫
联系客服