前段时间在做一个将oralce代码转换hive代码项目开发时,遇到了一个问题,问题描述如下:
(A)
Hive:
Select concat(round(1/3,4)*100,'%') from v_table limit 1;
Output:33.33%
Oracle:
select concat(round(1/3,4)*100,'%') from dual;
Output:33.33%
(B)
Hive:
Select concat(round(4/3,4)*100,'%') from v_table limit 1;
Output:133.32999999999998%
Oracle:
select concat(round(4/3,4)*100,'%') from dual;
Output:133.33%
B中hive输出的字符串长度过长导致插入oracle表时出错(oracle该字段定义为VARCHAR2(13)), 想到查看hive系统函数round的源码来探个究竟。Hive所有的函数源码可以通过解压hive-0.1x-0.tar.gz(附带源码)查看(感觉是编写udf很好的参考资料):
路径: $hive_dir/src/ql/src/java/org/apache/Hadoop/hive/ql/udf,其中的UDFRound.java 中就是定义了round函数的功能,该类重载多个evaluate方法,找到对应本例中的版本:
public DoubleWritableevaluate(DoubleWritable n, IntWritable i) {
if ((n == null) || (i == null)) {
return null;
}
return evaluate(n, i.get());
}
其中evaluate调用以下方法:
private DoubleWritableevaluate(DoubleWritable n, int i) {
double d = n.get();
if (Double.isNaN(d) || Double.isInfinite(d)){
doubleWritable.set(d);
} else {
doubleWritable.set(BigDecimal.valueOf(d).setScale(i,
RoundingMode.HALF_UP).doubleValue());
}
return doubleWritable;
}
hive round函数采用java类库中BigDecimal(高精度十进制浮点数)来缩减小数位,但是最终返回的是二进制浮点型double,由于二进制浮点数存在精度问题,表示某些数是近似表示,例如二进制浮点数就不能用有限位表示十进制的0.1,因此导致double的四则运算可能会出现随机的误差,如B中所示。下面代码重现了hive在AB表现的不同:
import java.math.BigDecimal;
import java.math.RoundingMode;
public class Test {
public double round(double d,int i){
double ret = 0.0;
if (Double.isNaN(d)|| Double.isInfinite(d)) {
ret = d;
} else {
ret = BigDecimal.valueOf(d).setScale(i,RoundingMode.HALF_UP).doubleValue();
}
return ret;
}
public static void main(String[] args) {
// TODO Auto-generated methodstub
Test t = new Test();
System.out.println(t.round(1/3.0,4)*100);
System.out.println(t.round(4/3.0,4)*100);
System.out.println(1.3333*100);
System.out.println(0.3333*100);
}
}
Output:
33.33
133.32999999999998
33.33
133.32999999999998
高级语言如java (BigDecimal类)、python(decimal模块)等都提供了decimal表示,hive在0.11版本开始也引入decimal类型。十进制浮点数与二进制浮点数常规标准IEEE754的不同点表现在它的底数为10,因此decimal需要独立实现其四则运算算法,因此一般情况下其运算效率比较二进制浮点数也较低。
A B中Oracle的round函数并不会有这样问题,可能是oracle的内置数据类型保证其精度不会出现误差。
以上是我的一些简单的思考,有什么阐述不对或者有什么更加透彻的理解,欢迎交流!
[1] http://my.oschina.net/jackieyeah/blog/205505
[2] http://www.javaweb.cc/JavaAPI1.6/ (search BigDecimal)