不小心又中了decimal的埋伏——重新捋一下decimal的相关知识

时间:2021-11-23 05:37:00
大家都知道在程序语言和数据库中各种数字类型decimal,numeric,float,double,real……,有时候不小心就会出现问题,除了掌握基本的数据存储原理,还要仔细了解各个语言和产品里的细节差别,即便如此,一不小心还是有可能中了埋伏。
在SQLserver中Decimal可以用来存储整数,也可以用来存储小数,而且精度高,所以我常常用来保存像销售类的数据相关字段。

情景再现

某数据表结构如下:
create table demo
(
id bigint,
amount decimal(18,5),--总金额
quantity decimal(18,5)--数量
)
在编写一个功能其中使用了一句
select convert(decimal(18,2),amount)/convert(decimal(18,2),quantity) from demo where quantity>0.00

问题解决和分析

执行过程中总是报“被零除的错误”,看看代码觉得没有什么问题,仔细看后才明白,原本想我要的结果只需要保留2位小数,所以执行转换后再行计算,还有一方面是quantity>0.00的判断和前面的转换不匹配造成的。尽管0.00001虽然大于0.01符合筛选条件
但转化后就变成0.00了,所以才会出现上面的错误。
sql 2005中decimal的能够存储的数字范围是- 10^38 +1 到 10^38 - 1(见参考1),用科学计数法看不直观它的威力(^-^),最大正数就是4个1亿相乘以后再乘以1万减1。这是非常大的数字了。
回到正题,那么我们定义的两个字段都是decimal(18,5),表示精度为18,小数为5位, 但是要注意的是ms sql 中位数就是10进制的位数,也就是说decimal(18,5)表示-9999999999999.99999到9999999999999.99999之间的数字。这样decimal(18,5)和decimal(18,2)就有较大差别了,可以认为它们是不同的数据类型(见参考2),在两种数据数据类型之间转换,自然就可能有所损失。
解决就简单了,直接改为计算后在转化就行了。
select convert(decimal(18,2)/(amount/quantity)) from demo where quantity>0.00000

反思

其实解决以上问题很简单。之所以“小题大做”,主要还是觉得对数据类型掌握还不够细,总是觉得那么数据类型,大概对就行了,差不多就行,实际上这是不对的。

延伸思考,看看C#中decimal

在开发中常常C#来读取数据库存储的值,就必要要定义相应的变量来存取。在C#里decimal是128位的精确数据类型,大致范围是 ±1.0 × 10^-28 到 ±7.9 × 10^28,比SQL Decimal的最大范围要小一些,但是当前人类的99.999999……999999999%的应用应该是完全可以满足的(见参考3)。decimal在计算机中是如何存储? (见参考4)。