本周工作中遇到了一个非常奇怪的问题,一开始是3张报表的数字对不起来,经查证发现有一张报表的数字有误,差1,后来请客户查询数据库,别提有多费劲了,客户那边的正式机的数据库是在别的楼层上,只能去那个机器上去查,而且机器上的USB全部锁了,所以查询DB的话只能手动去打代码,很长的就不太好意思让客户去打了,因为那个查询的SQL是一个存储过程,虽然很长但是这个代码客户是可以看到的,我用打电话的方式告诉客户怎样把这个存储过程修改成一个查询语句,而且又加了几个栏位去验证两个栏位的各自的值,发现各自的值和我算的没有差别,这个SQL中的查询两个栏位相乘后为167161.5,四舍五入后发现不会进位。是不是很奇怪呢?
(客户也觉得很奇怪,还请她们那边的DB专家看了看,t1=2110625,t2=0.0792,t3,t4,这4个栏位都是我让她加上的,去验证数据的正确性,看查询出数据的带有阴影的那一行)t1*t2=t3 可是t3四舍五入后(lastdde这个栏位)就不进位)客户还专门很给力的拍了张照片传给了我,如下图:
我又看了两个栏位的类型一个是money类型为2110625.00,一个是float类型0.0792,后来自己声明了这两个类型,然后相乘,再四舍五入,发现竟然进位了。网上还说它的四舍五入是采用的所谓的“银行家算法”,即如果该位为5时,则看5前面的数是奇数还是偶数再决定要不要进位,这样进行大量数据统计时误差才会更小。声明的两个类型,然后相乘再四舍五入,发现竟然进位,可见它不是采用的所谓的银行家算法,而是中国传统的四舍五入,但图中的lastdde这个栏位又,如何解释呢?网上还说float类型是有问题的,查了一下SQLServer2008的帮助文档,上面说不保证float类型表示的数字是精确的,就根据这句话,我就改了那个栏位的类型,0.0792改成了decimal(5,4),然后请客户填了维护数据库的的申请单,直接在正式执行该存储过程。然后结果就对了,可是一直不是很明白,虽然问题解决了。近一两年一直没有发生这种现象是因为t2这个栏位之前是两位小数,最近改成4位小数了。直到后来去看SQLServer2008技术内幕(T-SQL查询)这本书时,才明白了,数字的计算机表示对算数运算来说不是真实的。
FLOAT类型的官方描述:
用于表示浮点数值数据的大致数值数据类型。浮点数据为近似值;因此,并非数据类型范围内的所有值都能精确地表示。
上面已经告诉我们了一句话,就是不要太相信它喔。
TSQL提供了一个专门的代表实数的数据类型。事实上它提供了两种实数类型:REAL和FLOAT,不过这些数据类型和SQLSERVER的其他数据类型(其中一些类型据称是精确的类型)都不是严格数学意义上的实数系统。
数字的计算机表示对算数运算来说不是真实的,虽然它们可以满足大多数需求,但不能为所有问题提供“正确的答案”。
下面举例说明:
Real类型的不真实性:
这个不符合乘法的结合律a(bc)=(ab)c,但是对于SQLSever中实数和运算的表示,这两个式子则不相等,SQLServer在这一点上并没有任何错误,这样的结果符合浮点运算的重要IEEE标准。
Float类型的不真实性:
12.055进位应该是12.06结果却为12.05
另外的还补充一点,就是.NET中的Math.Round()这个函数默认的是采用银行家算法,这个函数还有哥带有枚举值的参数,这个参数可以指定使用哪种四舍五入的方法(其中包括传统的四舍五入的方法和采用银行家算法的四舍五入的方法)
如果使用 money 或 numeric 数据类型,那么,内部转换为 float 可能会降低精度。
所以在进行精确财务计算时,请尽量少使用float和real这样的数字类型,虽然这两种数据类型速度要快。