这片博客本来想的是只写pls_integer与number之间的性能比较,但是提笔之后我又想写更多我的思考过程,于是索性写一篇关于oracle数值类型的文章。干货太多,未免看得乏味,本篇我尝试用一种聊天谈话的方式行文,希望同行者可以在一种轻松的氛围中和我一起来探讨oracle的诸多数值类型,好吧,那我就先抛砖引玉了。
1、number
我最先接触的数值类型就是老大哥number,它定义为number(p,s),p是精度,最大38位,s是刻度范围,可在-84~127间取值。举个栗子(例子):number(4,2),指的就是小数点整数+小数部分一共有4位,不包含小数点位,也就是说整数部分最多两位,如果小数部分多于2位,会做四舍五入处理(对于sqlserver和
mysql也会做四舍五入处理),如果小数部分不足2位的话,则会补0(对于sqlserver也会补0,但是mysql不会),如果不写p,s,那么就默认为38。number类型是oracle最常用的数字类型了,在很长一段时间之内,我以为数值类型就只有number,因为number类型足以处理几乎全部的数字类型了。但是之后在oracle道路上继续行走的过程中,我又认识了其他衍生的数值类型。
2、integer
其实最开始看到这个数值类型的时候,同很多人一样,我也认为它是number类型的子类型,最大38位,对于小数做自动四舍五入处理,但是后来我发现不完全是这样的。作为number的子类型不假,一般情况下,integer是作为存储整数值存在的,在需要存储整数值的时候我们会想到这个数值类型,但是它的最大位可不是38位,在插入的测试数值看到,它已经远远超过38位了,如图:
可以看到,对于integer只能存储38位的言论可谓是直接推翻。接下来我们来看坊间盛传的,"integer只能存储整数,对于小数会做四舍五入处理",来看一个例子:
create or replace procedure integer_test(a integer)
as
b integer := a;
begin
dbms_output.put_line(b);
end;
输出结果如图:
看到这个结果的时候,相信很多同学都已经凉了,简直是颠覆了以往的认识啊,的确,从这个例子可以看出,integer是number的子类型,但是它不会强制进行四舍五入处理,同时它最大位数大于38位。
3、int,numeric,decimal
这三种数值类型是oracle为了兼容其他数据库而存在的,同样都是number的子类型。在进行类型声明的时候,如果直接声明为int,numeric,decimal,它会被直接存为integer,处理方式同integer一模一样;numeric和decimal如果带有精度和刻度范围,那么会被存为number(p,s),处理方式同number(p,s),int类型只能被声明为int,不能带精度和刻度范围,所以只能当作integer来处理。
4、float
float也是number的子类型,float(n),n指的是精度,是二进制精度,这里可不是十进制精度哦,计算公式为:binary precision=ceil(b*0.30103)
,float没有刻度范围.n的范围为1~126。那么怎么计算存入数据库中的数值呢,比如:float(2),那么它的精度是ceil(2*0.30103)=1,如果存入13.5,那么应该是1.35*10^1,精度为1,1.35四舍五入变为1,所以就是1*10^1=10存入的结果就是10;如果是19.5,那么应该是1.95*10^1,精度为1,1.95四舍五入为2,就是2*10^1,结果存入20。如果没有精度,那么映射为number类型,直接存入此值。
oracle没有直接为double类型的数值类型。
5、binary_integer和pls_integer
这个数字类型只能用于PL/SQL,他们相比number来说更具优势,首先是占有较少的存储空间,其次他们是直接通过硬件来计算的,也就是直接通过CPU进行计算,而不用经过转换,number类型在计算的时候,还要先被转换为二进制,所以在计算速度上,binary_integer和pls_integer比number性能好很多,建议在PL/SQL中使用这两种数据类型来代替number,你将会得到很大的惊喜。
5、binary_float和binary_double
这两种数值类型10g之后才出现,binary_float可以存储一个单精度的32位浮点数,binary_double可以存储一个双精度的64位浮点数,
binary_float和binary_double占有更少的存储空间,同样的,binary_float和binary_double也是直接通过硬件计算的,所以它的计算效率更高,而且它们比起number来说可以存储更大或者更小的数值,但是这两种数值类型也会存在精度不准的情况。
6、写到浮点型,我需要说一说浮点型存数不精确的原因:
首先,我们要知道浮点型在计算机是怎么存储的,比如:要存入13.75,首先将整数部分转为二进制,13转为二进制是1101,接下来将小数部分转为二进制,小数部分是0.75,将小数部分乘以2,取结果的整数部分为二进制的一位,然后继续取结果的小数部分乘以2重复,一直到小数部分全部为0结束,在这个过程中,是会出现循环乘不尽的情况,但是因为浮点数的小数位是确定的(float是23位,double是52位),所以到了指定的小数位就会停下来,这就是浮点数存数不精确的原因,可以看到如果可以在小数位范围之内乘尽的话,那么结果就存储的准确了。来看看小数部分(0.75)转为二进制的过程:
0.75*2=1.5 取1
0.5*2=1 取1
乘到这里,即0.5*2=1,1已经没有小数部分了,所以到这里也就停止了,最后的结果是0.11,与上面整数部分合起来,那么13.75转换为二进制为1101.11,即1.10111*2^3,1.10111是小数部分,2为底,6为指数部分,这个1.10111*2^3就是转换好的二进制浮点数,接着就是将这个二进制浮点数存入计算机了。
将其存入计算机内,将会使用浮点表示法,分为三大部分:
第一部分:符号位,占用1位,用来区分正负数,0为正数,1为负数;对于1.10111*2^3,它是正数,所以符号位是0
第二部分:指数位,float占用8位,double占用11位,用来表示指数;对于1.10111*2^3,指数位是3
第三部分:小数位,float占用23位,double占用52位,用来表示小数,不足位数补0;对于1.10111*2^3,小数位是10111
所以对于float,符号位+指数位+小数位=1+8+23=32位,对于double,符号位+指数位+小数位=64位,同时可以看到,指数位决定了大小范围,小数位决定了精度。因为指数位有可能是正数,也有可能是负数,负数算起来比正数麻烦,所以为了减少计算的麻烦,在存储指数的时候,需要将它存储位无符号的整数,所以在指数位上会加一个偏移量,float的偏移量是127,double的偏移量是1023,如果需要转换为十进制的话,到时候指数减去相应的值就可以了。那么对于13.75(float),本来它的二进制指数值是3,现在就成了3+127=130,转换为二进制是10000010,最终:13.75(float)被存储为:符号位 指数位 小数位(不足位补0)=0 10000010 10111 00000 00000 00000 000
7、万丈高楼平地起,有时候我们太过追求一些高级语法,或者高大尚的东西,反而在基础的东西上掌握不牢,实际上,数据库的基础数据类型往往没有表面上看的那么简单,其实任何语言都是一样,只有掌握好基础知识,做一个基础精通者,那么,在我们进阶的道路上就会水到渠成。写到最后,才发现整体下来,整篇文章也没有那么轻松,严肃的东西写惯了,一下子不太适应写轻松的东西,没事,能抛砖引玉就好。