Oracle数据类型之number

oracle的number类型是oracle的内置类型之一,是oracle的最基础数值数据类型。在9iR2及其以前的版本中只支持一种适合存储数值数据的固有数据类型,在10g以后,才出现了两种新的数值类型,即推出本地浮点数据类型(Native Floating-Point Data Types): BINARY_FLOAT(单精度32位)和BINARY_DOUBLE(双精度64位). 这些新数据类型都是基于IEEE二进制浮点运算标准,ANSI/IEEE Std 754-1985 [IEEE 754],使用这些类型时要加上文字f(BINARY_FLOAT)或者d(BINARY_DOUBLE),比如2.07f、3.000094d。

number数据类型

number类型的语法很简单:number(p,s):
p:精度位,precision,是总有效数据位数,取值范围是38,默认是38,可以用字符*表示38。
s:小数位,scale,是小数点右边的位数,取值范围是-84~127,默认值取决于p,如果没有指定p,那么s是最大范围,如果指定了p,那么s=0。
p:is the precision,or the total number of digits. Oracle guarantees the portability of numbers with precision ranging from 1 to 38.
s:is the scale, or the number of digits to the right of the decimal point. The scale can range from -84 to 127.

number类型的p和s,与其底层存储完全没有关系,根本不会影响数据在磁盘上如何存储,它只会影响允许哪些值以及数值如何舍入,你可以认为其是对数据的“编辑”。简单的说,精度位p表示数值最多能有多少个有效数字,而小数位s表示最多能有多少位小数。换句话说,p表示一共有多少位有效数字(即小数点左边最多有p-s位有效数字),s表示小数点右边有s位有效数字。如number(5,2)类型的数据,就表示小数点左边最多有3位有效数字,右边最多有2位有效数字,加起来就是最多有5位有效数字,超过这个范围的数字就不能正确的存储下来,注意这里说的是不能正确存储,但并不是不能存储。

最高整数位数=p-s
s正数,小数点右边指定位置开始四舍五入
s负数,小数点左边指定位置开始四舍五入
s是0或者未指定,四舍五入到最近整数
当p小于s时候,表示数字是绝对值小于1的数字,且从小数点右边开始的前s-p位必须是0,保留s位小数。

p>0,对s分2种情况:
1. s>0
精确到小数点右边s位,并四舍五入。然后检验有效数位是否<=p;如果s>p,小数点右边至少有s-p个0填充。
2. s<0
精确到小数点左边s位,并四舍五入。然后检验有效数位是否<=p+|s|

具体数据可参考下表

Value

Datatype

Stored Value

123.2564

NUMBER

123.2564

1234.9876

NUMBER(6,2)

1234.99

12345.12345

NUMBER(6,2)

Error

1234.9876

NUMBER(6)

1235

12345.345

NUMBER(5,-2)

12300

1234567

NUMBER(5,-2)

1234600

12345678

NUMBER(5,-2)

Error

123456789

NUMBER(5,-4)

123460000

1234567890

NUMBER(5,-4)

Error

12345.58

NUMBER(*, 1)

12345.6

0.1

NUMBER(4,5)

Error

0.01234567

NUMBER(4,5)

0.01235

0.09999

NUMBER(4,5)

0.09999

0.099996

NUMBER(4,5)

Error


里面发生错误的行有的是因为源数据超过了可以表示的范围,有的是因为进行小数四舍五入后超过了可以表示的范围。

以下是一些例子

1. s>0

精确到小数点右边s位,并四舍五入。然后检验有效数位是否<=p;

ZWF.YUDONG>create table t_n(id number(5,2));

Table created.

ZWF.YUDONG>insert into t_n values(123.45);

1 row created.

ZWF.YUDONG>insert into t_n values(123.455);

1 row created.

ZWF.YUDONG>select * from t_n;

        ID
----------
    123.45
    123.46

2 rows selected.

ZWF.YUDONG>insert into t_n values(1.234);

1 row created.

ZWF.YUDONG>select * from t_n;

        ID
----------
    123.45
    123.46
      1.23

3 rows selected.

ZWF.YUDONG>insert into t_n values(.001);

1 row created.

ZWF.YUDONG>select * from t_n;

        ID
----------
    123.45
    123.46
      1.23
         0

4 rows selected.

ZWF.YUDONG>insert into t_n values(1234.56);
insert into t_n values(1234.56)
                       *
ERROR at line 1:
ORA-01438: value larger than specified precision allowed for this column


如果s>p,小数点右边至少有s-p个0填充。

ZWF.YUDONG>create table t_n(id number(4,5));

Table created.

ZWF.YUDONG>insert into t_n values(1);
insert into t_n values(1)
                       *
ERROR at line 1:
ORA-01438: value larger than specified precision allowed for this column


ZWF.YUDONG>insert into t_n values(.1);
insert into t_n values(.1)
                       *
ERROR at line 1:
ORA-01438: value larger than specified precision allowed for this column


ZWF.YUDONG>insert into t_n values(.01);

1 row created.

ZWF.YUDONG>commit;

Commit complete.

ZWF.YUDONG>select * from t_n;

        ID
----------
       .01

1 row selected.

ZWF.YUDONG>insert into t_n values(.001);

1 row created.

ZWF.YUDONG>insert into t_n values(.0001);

1 row created.

ZWF.YUDONG>insert into t_n values(.00001);

1 row created.

ZWF.YUDONG>insert into t_n values(.000001);   --超过刻度存储0

1 row created.

ZWF.YUDONG>select * from t_n;

        ID
----------
       .01
      .001
     .0001
    .00001
         0


10 rows selected.

ZWF.YUDONG>col dp for a50
ZWF.YUDONG>select id,dump(id) dp,length(id),vsize(id) from t_n;  --vsize和dump的是字节数,length是数值实际位数(含小数点)

        ID DP                                                 LENGTH(ID)  VSIZE(ID)
---------- -------------------------------------------------- ---------- ----------
       .01 Typ=2 Len=2: 192,2                                          3          2
      .001 Typ=2 Len=2: 191,11                                         4          2
     .0001 Typ=2 Len=2: 191,2                                          5          2
    .00001 Typ=2 Len=2: 190,11                                         6          2
         0 Typ=2 Len=1: 128                                            1          1

5 rows selected.


2. s<0

精确到小数点左边s位,并四舍五入。然后检验有效数位是否<=p+|s|

ZWF.YUDONG>create table t_n(id number(5,-2));

Table created.

ZWF.YUDONG>insert into t_n values(12345);

1 row created.

ZWF.YUDONG>select * from t_n;

        ID
----------
     12300

1 row selected.

ZWF.YUDONG>insert into t_n values(123456);

1 row created.

ZWF.YUDONG>insert into t_n values(1234567);

1 row created.

ZWF.YUDONG>select * from t_n;

        ID
----------
     12300
    123500
   1234600

3 rows selected.

ZWF.YUDONG>insert into t_n values(12345678);
insert into t_n values(12345678)
                       *
ERROR at line 1:
ORA-01438: value larger than specified precision allowed for this column

oracle的number类型存储结构

oracle采用变长存储number数据类型(按一定规则进行转换成2进制编码格式存储)。

oracle数据库中存储的number类型包含3个部分: HEAD部分, DATA部分, 符号位。

对正数来说, 符号位省略, 对0来说, oracle存储的是X80(128)。

ZWF.YUDONG>select dump(0) from dual;

DUMP(0)
----------------
Typ=2 Len=1: 128

1 row selected.

ZWF.YUDONG>select dump(1) from dual;

DUMP(1)
------------------
Typ=2 Len=2: 193,2

1 row selected.

ZWF.YUDONG>select dump(-1) from dual;

DUMP(-1)
-----------------------
Typ=2 Len=3: 62,100,102  

1 row selected.

HEAD部分为一个字节8位, 就是前面看到的128, 193,62。由该部分我们可以看出number类型的基本信息,因为设计这种存储格式的时候, oracle希望以十六进制00-FF来表示所有
的number, 所以为了编码的对称, 首先将number分为正负, 所以以00-FF的中间位置80, 也就是十进制的128来表示0, HEAD部分小于80,即为负数,大于80即为正数。ORACLE再次对
00-80, 80-FF进行对分:

00-3E 表示: number <= -1
3F-7F 表示: -1 < number < 0
81-C0 表示: 0 < number < 1
C1-FF 表示:number >= 1

从HEAD部分我们可以也看出数据的位数信息,是否含有小数,可以根据HEAD的信息判断小数点的位置。由于数据部分低位2的n次方位个0是不被存储的,数据展现的时候oracle
根据HEAD的信息给补充末位的0。

ZWF.YUDONG>select dump(123456789) from dual;

DUMP(123456789)
------------------------------
Typ=2 Len=6: 197,2,24,46,68,90 --197(C5)的含义:表示数字123456789大于1,197-193(数字1占用2个字节该值为193) = 4 ,所以该数字占用6(2+4)个字节。

1 row selected.


然后,我们再来看数据部分, ORACLE对十进制的数字(整数部分,小数部分正好相反)是两位两位进行存储的(从右往左的顺序), 例如对1234, ORACLE会分别对12, 34进行存储.
所以只需要对(+-)1-99进行编码

1 --- 99 分别用十六进制2-64表示,就是2-100,

-1--- -99 用十六进制64-2表示,就是100-2

ZWF.YUDONG>select dump(12345) from dual;

DUMP(12345)
------------------------
Typ=2 Len=4: 195,2,24,46  --数据部分2,24,46 表示 (2-1=1,24-1=23,46-1=45);HEAD部分表示12345 >= 1,占用195-193+2=4字节。

1 row selected.


SYS.YUDONG>select dump(1100) from dual; 

DUMP(1100)
-------------------
Typ=2 Len=2: 194,12       --如果从右边起,连续2的n次方位为0,oracle一次排触(不存储)只是位数加1。可以对比dump(11)的情况看看。

1 row selected.

SYS.YUDONG>select dump(11) from dual;

DUMP(11)
-------------------
Typ=2 Len=2: 193,12        --这里数据部分和1100是一样的,末位的2个0没有实际存储,长度193比194小1。

1 row selected.

--对于含小数(负数、整数2种情况)的情况:

1、负数

SYS.YUDONG>select dump(-1.2) from dual;

DUMP(-1.2)
--------------------------
Typ=2 Len=4: 62,100,81,102    --HEAD=62(3E)表示该数值小于等于-1;数据部分:整数部分的-1存储为100,小数部分从左往右2位一结合,不足2位后边补一个1。
                              对应关系变为9,8...1表示1,2...9,看下面几个例子,如果足2位,还是按照上边说的规律(-1--- -99 用十六进制64-2表示,就是100-2)。

1 row selected.


ZWF.YUDONG>select dump(-2.1) from dual;

DUMP(-2.1)
-------------------------
Typ=2 Len=4: 62,99,91,102

1 row selected.

ZWF.YUDONG>select dump(-2.2) from dual;

DUMP(-2.2)
-------------------------
Typ=2 Len=4: 62,99,81,102

1 row selected.

ZWF.YUDONG>select dump(-2.9) from dual;

DUMP(-2.9)
-------------------------
Typ=2 Len=4: 62,99,11,102

1 row selected.

ZWF.YUDONG>select dump(-2.12) from dual;

DUMP(-2.12)
-------------------------
Typ=2 Len=4: 62,99,89,102

1 row selected.

ZWF.YUDONG>select dump(-2.13) from dual;

DUMP(-2.13)
-------------------------
Typ=2 Len=4: 62,99,88,102

1 row selected.

ZWF.YUDONG>select dump(-2.123) from dual;

DUMP(-2.123)
----------------------------
Typ=2 Len=5: 62,99,89,71,102

1 row selected.


2、正数

SYS.YUDONG>select dump(1.222) from dual;

DUMP(1.222)
------------------------
Typ=2 Len=4: 193,2,23,21      --HEAD=193(C1)表示该数字大于等于1;数据部分:整数部分存储2(2-1=1),小数部分从左往右2位一结合,23(23-1=22)表示22,后边还剩下一个2,
                                不足2位的末尾补充一个1,也就是等于1.2220

1 row selected.

ZWF.YUDONG>select dump(1.2220) from dual;

DUMP(1.2220)
------------------------
Typ=2 Len=4: 193,2,23,21

1 row selected.


符号位: 用的是(+-)1-99都不可能用到的编码66(102)来表示,有资料说为了处理排序问题(未加考证)。根据HEAD部分可以做初步判断,根据我们说的HEAD部分的四个范围,
如果2个数值不在一个范围,立即可以看出大小,如果在一个范围其实也可以根据其正负+绝对值来进行排序了,正数绝对值大的就大,负数则相反,为何还要用到这个符号位?

本地浮点类型

另外再说一下两个数值类型本地浮点数据类型(binary_float与binary_double)

本地浮点数据类型最大的特点就是比NUMBER类型效率更高
硬件运算/数学运算快 5– 10 倍
占用更少的内存/磁盘空间(5/9 字节与 1 – 22 字节)
BINARY_DOUBLE 值范围更大(e308 与 e125)
无需类型转换(使用与字节顺序无关的存储格式)

下面程序是使用欧拉级数计算圆周率∏:
∏ = sqrt ( 6 * ( 1 + 1/2*2 + 1/3*2 + ... ) )

方法一:使用NUMBER类型

create or replace procedure Euler_Pi_Number is

subtype My_Number is number;

zero constant My_Number := 0.0;
one constant My_Number := 1.0;
two constant My_Number := 2.0;
six constant My_Number := 6.0;
toler constant My_Number := 0.00000000001;
root_toler constant My_Number := toler/1000.0;

root My_Number;
prev_root My_Number;
prod_over_six My_Number;
prod My_Number;
pi My_Number;
prev_pi My_Number;
step My_Number;

begin
pi := one;
prev_pi := zero;
prod_over_six := zero;
step := zero;
while pi - prev_pi > toler
loop
prev_pi := pi;
step := step + one;
prod_over_six := prod_over_six + one/(step*step);
prod := six*prod_over_six;
prev_root := prod;
root := prod/two;
while Abs(root - prev_root) > root_toler
loop
prev_root := root;
root := (root + prod/root)/two;
end loop;
pi := root;
end loop;
end Euler_Pi_Number;
/


方法二:使用BINARY_DOUBLE类型

create or replace procedure Euler_Pi_Binary is

subtype My_Number is binary_double;

zero constant My_Number := 0.0d;
one constant My_Number := 1.0d;
two constant My_Number := 2.0d;
six constant My_Number := 6.0d;
toler constant My_Number := 0.00000000001d;
root_toler constant My_Number := toler/1000.0d;

root My_Number;
prev_root My_Number;
prod_over_six My_Number;
prod My_Number;
pi My_Number;
prev_pi My_Number;
step My_Number;

begin
pi := one;
prev_pi := zero;
prod_over_six := zero;
step := zero;
while pi - prev_pi > toler
loop
prev_pi := pi;
step := step + one;
prod_over_six := prod_over_six + one/(step*step);
prod := six*prod_over_six;
prev_root := prod;
root := prod/two;
while Abs(root - prev_root) > root_toler
loop
prev_root := root;
root := (root + prod/root)/two;
end loop;
pi := root;
end loop;
end Euler_Pi_Binary;
/


SQL> set timing on
SQL> exec Euler_Pi_Number;

PL/SQL 过程已成功完成。

已用时间: 00: 00: 11.59
SQL> exec Euler_Pi_Binary;

PL/SQL 过程已成功完成。

已用时间: 00: 00: 02.09


上面例子中近似300,000次迭代计算,NUMBER类型花费11.59秒,BINARY_DOUBLE类型花费约2.09秒,性能提高大约5.5倍.

结论:在版本10g之后写一些偏数字科学运行量巨大的存储过程、函数时,对于浮点数字类型要优先考虑使用本地浮点数据类型

Number类型是oralce的数值类型,存储的数值的精度可以达到38位。Number是一种变长类型,长度为0-22字节。取值范围为:10^(-130) —— 10^126(不包括)。以十进制格式进行存储的,它便于存储,但是在计算上,系统会自动的将它转换成为二进制进行运算的。

Number(p,s):

P和s都是可选的。

P指精度(precision),即总位数。默认情况下精度为38。精度的取值范围为1~38。

S指小数位(scale),小数点右边的位数。小数点位数的合法值为-84~127。小数位的默认值由精度来决定。如果没有指定精度,小数位默认为最大的取值区间。如果指定了精度,没有指定小数位。小数位默认为0(即没有小数位)。

精度和小数位不会影响数据如何存储,只会影响允许哪些数值及数值如何舍入。

存储格式:

数字类型内部存储是一个字节的变长阿拉伯数字数组。

数字类型有以下格式:

< [length]>,sign bit/exponent,digit1,digit2,...,digit20

0 digit1 21

指数字节

数值

数值

数值

第0个字节:

为符号和指数字节(sign bit/exponent ),由三部分构成:

(1)符号位:是整个字节的最高位(128)

如果是0,则数字是负数

如果是1,则数字是正数或者0

(2)偏移量:总是65

(3)指数:

范围为-65到62

标示该数字100为基础的科学记数法

对于第0个字节的值,则有:正数>128;负数<128;零=128

记ZV=第0个字节的值,指数值为E则有:

若ZV=200,E=200-128-65=7

如果第一个字节大于128,那么数字是正的:

E=ZV- 128 -65=ZV-193

如果第一个字节小于128,那么数字是负的:

E=(255-ZV)-128-65=62-ZV

数值字节(Digits):

大部分数字的最高位是digit1

使用100作为基数(每一个数字是0-99之间)

对于正数:

实际值=存储值-1

对于负数:

实际值=101-存储值

标志102位于最后

例:

11.11的存储格式为:

193,12,12

193>128,为正数,指数E=0,11(实际值)=12(存储值)-1

-11.11的存储格式为:

62,90,90,102

62<128,为负数,指数E=0,11(实际值)=101-90(存储值)

因为数字是基于100的,因此最大的数字是99.指数从-65到62,因此,oracle的number的范围为:1*100(-65)=1*10^(-130)到99*100(62)+99*100(61)……无限接近1*10^(126)。

正数的解读

数字为正数,第一个字节必须大于128(0x80)

第一步:指数= ZV(第0个字节的值)-193

第二步:每一个其他的数字都减1

第三步:从基于100的指数转换到基于10的指数

每一个结果通过100^(EXP-N)其中

EXP是第一步中得到的指数

N是数字的位置的顺序号(最高位的N=0)

第四步:将所有的值相加

例:1234.12的存储:194,13,35,13

求值过程:

194>128,正数,E=1

实际值1234.12=(13-1)*100^1+(35-34)^0+(13-1)^(-1)

负数的解读

数字为负数,第一个字节必须小于128(0x80)

第一步:指数=62-ZV(第0个字节的值)

第二步:每一个数字被101减

第三步:将基数从100转换为10

每一个数字乘以100^(EXP-N)这里

EXP是第一步中的指数值

N是数字位置顺序值(最高位为0)

第四部:确认尾巴上的102已经抛弃

第五步:将所有的值相加

如果数字标示超过21字节,则最后的102不被要求,oracle不会使用22字节来标示的。Oracle将自己会考虑所有的字节。

例:-1234.12的存储格式:61,89,67,89,102

求值过程:

61<128,负数,E=62-61=1

实际值-1234.12=-( (101-89)*100^1+(101-67)*100^0+(101-89)*100^(-1) )

 

总结

----------------------------------------------------------

SQL> select dump(-1234.12) from dual;                                                                                                                                           

DUMP(-1234.12)
----------------------------
Typ=2 Len=5: 61,89,67,89,102

SQL> select -((101-89)*power(100,62-61)+(101-67)*power(100,62-61-1)+(101-89)*power(100,62-61-1-1)) from dual;                                                                   

-((101-89)*POWER(100,62-61)+(101-67)*POWER(100,62-61-1)+(101-89)*POWER(100,62-61-1-1))
--------------------------------------------------------------------------------------
                                                                              -1234.12

SQL> select dump(1234.12) from dual;                                                                                                                                            

DUMP(1234.12)
-------------------------
Typ=2 Len=4: 194,13,35,13

SQL> select (13-1)*power(100,194-193)+(35-1)*power(100,194-193-1)+(13-1)*power(100,194-193-1-1) from dual;                                                                      

(13-1)*POWER(100,194-193)+(35-1)*POWER(100,194-193-1)+(13-1)*POWER(100,194-193-1-1)
-----------------------------------------------------------------------------------
                                                                            1234.12

SQL>

-----------------------------------------------------------------------------------------------------------------------------------------------

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值