前言
公司数据库部分产品基于Hive进行开发,其中出现一个Bug。Oracle中表t1有个字段类型为decimal(38,0),当Hive这边执行了select Floor(col) from dblink,显示 Floor函数计算的精度太大。但是select Floor(col) from oracle_table 在Oracle这边本身就可以执行,而且Floor的意思是取小于等于的最大整数。
首先介绍一下精度问题:precision和scale
1.数据库中precesion和scale问题
(1)precision:表示有效数字(小数点左右总长度)
,如果没有指定precision的话,Oracle将使用38作为精度。
(2)scale:表示小数范围(小数点右边长度),
scale默认设置为0. 如果为负数,Oracle将把该数字取舍到小数点左边的指定位数。
例如:decimal(3,1),表示范围为99.9 ~ -99.9。
(3)多种情况讨论:
(Mysql和Oracle一样,
precision简称p,scale简称s
)
- 情况一:如果插入的数据小数点位数比s大,则对scale+1位置的小数进行四舍五入。当decimal(3,1)为1.15时,自动四舍五入为1.2.
- 情况二:当一个数的整数部分的长度 > p-s 时,Oracle就会报错
- 情况三:当s为负数时,Oracle就对小数点左边的s个数字进行舍入。
- 当decimal(10,-1)为9555.151时,四舍五入至小数点左边第一位,结果为9560
- 当decimal(10,-2)为9555.151时,四舍五入至小数点左边第二位,结果为9600
- 当decimal(10,-3)为9555.151时,四舍五入至小数点左边第三位,结果为10000
- 当decimal(10,-4)为9555.151时,四舍五入至小数点左边第四位,结果为10000
- 当decimal(10,-5)为9555.151时,四舍五入至小数点左边第四位,结果为0。
2.Hive源码中FloorUDF函数
Hive源码中的思路如下:
(1)首先在GenericUDFFloorCeilBase类中重新计算精度,precision=precision-scale+1
(2)其次在HiveDecimalUtils类中实例化decimal时,会验证precision和scale是否合法
但是这里忽略了致命的问题,因为HiveDecimal.MAX_PRECISION=38,当我定义一个列col类型decimal(38,0),真实数据为1。那么执行select Floor(col) from hive_table,真实数据应该为1,但这里会报错。因为GenericUDFFloorCeilBase重新计算precision=38-0+1=39,在validateParameter中会判断39>HiveDecimal.MAX_PRECISION从而报错
3.修改思路
Floor的意思是取小于等于的最大整数
-
如果scale>0,precision+1多了
-
如果scale<0, precision+1多了
-
如果scale=0. precision+1多了
-
当precision=scale时, +1有用 。比如decimal(1,1)取值0.1,Floor正确结果为0, 但0本身就是要占用一位有效数字
-
当precision<scale。比如decimal(2,3),inceptor这里做了判断创建表时precision不能小于scale
因此在GenericUDFFloorCeilBase
,我们只需要判断precision等于scale再决定+1.
precision=precision==scale?precision-scale+1:precision-scale