有一次计算一个数据的百分比,想把小数结果取2位,并拼接一个百分号展示在结果报表中。用到的sql如下
select concat(round(10230/1497409,4)*100,'%') from dual;
很奇怪局部数据并没有保留2位小数,比如上面的数据返回的是67.99999999999999
我计算了下上面的结果大概得到的数据为0.0068
select concat(round(0.0066 ,4)*100,'%') from dual;--0.66% select concat(round(0.0067 ,4)*100,'%') from dual;--0.67% select concat(round(0.0068 ,4)*100,'%') from dual;--0.6799999999999999% select concat(round(0.0069 ,4)*100,'%') from dual;--0.69%
由于计算值采用了concat函数,concat的多个参数为string类型,如果输入为bigint,decimal,double,datetime类型会隐式转化为string类型,并且返回的为string类型
也就是说有两种情况
1.round函数返回的数字cast为string后就丢失了精度。 2.round函数返回的数字就丢失了精度。
select round(0.0066 ,4)*100 from dual; --0.66 select round(0.0067 ,4)*100 from dual; --0.67 select round(0.0068 ,4)*100 from dual; --0.6799999999999999 select round(0.0069 ,4)*100 from dual; --0.69
上面的结果说明是round函数返回的数字就丢失了精度。
round函数是用来计算指定到小数点位数的四舍五入的值的,如果其第一个参数为double类型,那么函数计算的结果就是double类型,如果其第一个参数为Decimal类型,那么函数计算的结果就是decimal类型的,如果其第一个参数为string类型或者bigint类型,那么就会隐式转化为double类型。
单独计算round的返回值如下,说明double类型的round返回值为double
select round(0.0068 ,4) from dual; --0.0068
再计算乘法的结果,double和bigint相计算的时候也是会发生隐式转化的
select 0.0066*100 from dual;--0.66 select 0.0067*100 from dual;--0.67 select 0.0068*100 from dual;--0.6799999999999999 select 0.0069*100 from dual;--0.69
对于操作符号的运算,string,bigint和double都可以参与算术运算,string类型会转成double类型计算 当bigint和bigint进行除法运算的时候结果会返回double类型,当bigint和double共同计算的时候,big今天会转成doule类型,并且返回结果为double类型,
那么100变成了double类型,这时候问题就有点眉目了
select cast(0.0068 as double) from dual;--0.0068 select cast(100 as double) from dual;--100.0 select 0.0068*100.0 from dual;--0.6799999999999999
double浮点数运算的时候会有丢失精度的问题,这个是所有的浮点数运算的通病,我们可以再java下验证一下
public class TestDouble { public static void main(String args[]){ Double a=0.0068; Double b=100.0; System.out.println(a*b);//0.6799999999999999 Double c=0.0067; Double d=100.0; System.out.println(c*d);//0.67 BigDecimal e= new BigDecimal(0.0068); BigDecimal f=new BigDecimal(100.0); System.out.println(e.multiply(f));//0.679999999999999962113639284666533058043569326400756835937500 BigDecimal g= new BigDecimal(Double.toString(0.0068)); BigDecimal h=new BigDecimal(Double.toString(100.0)); System.out.println(g.multiply(h));//0.68000 } }
当然这个问题的最终解决方案很简单,不过以后再计算对精度要求比较高的数据的时候建议还是设计成decimal类型
select round(10230*100/1497409,4) from dual;
文章转载自wangming
更多问题欢迎加入MaxCompute钉钉群讨论