oracle internal number 数据类型存储

oracle的number类型是一种软件数据类型,在oracle软件本身中实现,我们不能使用固有硬件操作将两个number类型相加,这要在软件中模拟,number 类型的内部编码是2。oracle存储一个数的时,会存储尽可能少的内容来表示这个数,为此它会存储有效数字、用于指定小数点位置的一个指数,以及有关数值符号的信息(正或者负)。因此,数中包含的有效数字越多,占用的存储空间就越大。

    oracle底层存储number类型采用下面的这种形式:
sign bit/exponent,digit1,digit2,…,digit20
sign bit/exponent为标志位,digit1...digit20为数值位,可见number最多占据21个字节。
    一个数是可以使用科学计算法来正确表示的,如10=1*power(10,1),-101=-1.01*power(10,2),0.2=2*power(10,-1)。oracle就是通过这个原理来存储数据,它将任何一个它可以表示的数据转换成一个符号位、一个指数、一个有效数来存储,举个例子,123在oracle中是这样来存储的,+(1.23*power(100,1)),即它是以100为科学计算法的指数基数,为什么这样以及怎么来存储,请看下面。

    存储数据的第一个字节是标志位,这个字节里面会反映出这个数字的符号位以及表示这个数字的指数的符号位。采用科学计数法,任何一个实数S都可以描述为A.B×power(100,N),A表示整数部分(可能是正数,可能是负数),B表示小数部分,而N表示10的指数部分。那么oracle怎么用一个字节来表示这些信息呢?

    1、拿这个字节的最高位来表示正负,0表示负数,1表示正数。由此可见,这个数字在128这个部分被一切为2份,如果这个数字大于128,那么这个数就是正数,如果这个数字小于128,那么这个数字就是负数,如果这个数字等于128,那么这个数字就是0,如:
15:16:24 WATER@ lake>select 1,dump(1) from dual;
         1 DUMP(1)
---------- ------------------
         1 Typ=2 Len=2: 193,2

这里的第一个字节的标志位是193,大于128,故其为正数,另外可以看到,这里还有个偏移量为65,193=128+65+0。

15:16:30 WATER@ lake>select -1,dump(-1) from dual;
        -1 DUMP(-1)
---------- -----------------------
        -1 Typ=2 Len=3: 62,100,102

这里的第一个字节的标志位是62,小于128,故其为负数,另外可以看到,这里也还有个偏移量为65,62=255-(128+65+0)。

    偏移量65是这么得到的?请看下面。

    2、无论S是正数还是负数,它的科学表达法中幕的底数的指数也有正负之分,即N这个值有大于0和小于0两种情况,且在理论上说两种情况的量是相等的,因此把正数或者负数的情况中,各平均等分成两分,这样oracle所表示的数据的范围最广。将(1,127)和(128,255)各斩一半,偏移量应该是64啊,那为什么是65呢,因为这里的指数的底数是100,故要推一位得到65。
    现在,我们可以根据number第一个字节的标志位数来算出oracle数字的正负和科学表达法的指数的正负了:
如果这里的标志位数字大于等于128,那么这个数字就是正数,其科学表达式的指数计算方法是exponent=-128-65=-193;
如果这里的标志位数字小于128,那么这个数字就是负数,其科学表达式的指数计算方法是exponent=255-(-128-65)=62-;
    这样就得到了结论,标志位如果在(1,62)范围内表示是小于等于-1的负数,在(63,127)范围内表示是大于-1小于0的数,在(128,193)范围内表示的是大于0小于1的正数,在(193,255)范围内表示的是大于等于1的正数。

    这里的指数的底数为什么是100呢?后面会给予解释

    3、我们用实例来说明oracle的数字存储和计算表示方案,即数据位的计算方法。
16:51:56 SQL> select 0,dump(0),dump(0,16) from dual;
         0 DUMP(0)          DUMP(0,16)
---------- ---------------- ---------------
         0 Typ=2 Len=1: 128 Typ=2 Len=1: 80

DUMP(0)的结果是0x80(128),在前面已经提到,0只有高位表示位,没有数据位。由于0的特殊,既不属于正数,也不属于负数,因此使用高位表示位用80表示就足够了,不会和其它数据冲突,Oracle出于节省空间的考虑将后面数据部分省掉了。

16:52:22 SQL> select 1,dump(1),dump(1,16) from dual;
         1 DUMP(1)            DUMP(1,16)
---------- ------------------ -----------------
         1 Typ=2 Len=2: 193,2 Typ=2 Len=2: c1,2

dump(1)的结果是这样来算的:
(1) 标志位193,根据(193>128)可以算到这个数字是正数,根据(193-193=0)可以算到科学表达式的指数是0,100的0次方,即power(100,0)。
(2) 数据位2,由于其是正数,oracle存储该数的方法是该数值再加一,即1+1=2。为什么ORACLE使用0x2表示1,而不直接使用0x1表示1呢?Oracle每个字节表示2位数,因此对于这个2位数,出现的可能是0~99共100种可能,问题出在0这里。Oracle底层是用C语言实现的,我们知道二进制0在C语言中用作字符串终结符,Oracle为了避免这个问题,因此使用了0x1表示0,并依次类推,使用0x64表示99。
(3) 上面最核心的一句是“oracle每个字节表示2位数”,即数据的底层存储是每个字节表示2位数,即digit1...digit20,每个字节表示2位数,其表示的访问是从0到99。也因为这个原因,oracle的指数底数是100,因为这样刚好和显示的表现一致,从下面的例子,可以看得更明白。
(4) 
17:05:47 SQL> select 123456789.123,dump(123456789.123),dump(123456789.123,16) from dual;
123456789.123 DUMP(123456789.123)                  DUMP(123456789.123,16)
------------- ------------------------------------ ----------------------------------
    123456789 Typ=2 Len=8: 197,2,24,46,68,90,13,31 Typ=2 Len=8: c5,2,18,2e,44,5a,d,1f

它是如下计算的:
exponent指数底数=197-193=0xc5-0xc1=4,197-128>0得到这个数字是正数。
digits:
digit1 : 2-1 =0x02-0x01=1  > 1*100^4  = 1*power(100,4) = 1*power(100,4-0)=100000000
digit2 : 24-1=0x18-0x01=23 > 23*100^3 =23*power(100,3) =23*power(100,4-1)=23000000
digit3 : 46-1=0x2e-0x01=45 > 45*100^2 =45*power(100,2) =45*power(100,4-2)=450000
digit4 : 68-1=0x44-0x01=67 > 45*100^1 =67*power(100,1) =67*power(100,4-3)=6700
digit5 : 90-1=0x5a-0x01=89 > 89*100^0 =89*power(100,0) =89*power(100,4-4)=89
digit6 : 13-1=0x0d-0x01=12 > 12*100^-1=12*power(100,-1)=12*power(100,4-5)=0.12
digit7 : 31-1=0x1f-0x01=30 > 30*100^-2=30*power(100,-2)=30*power(100,4-6)=0.0030
因此 123456789.123=sum(100000000+23000000+450000+6700+89+0.12+0.0030)

    由此,得到计算正数的一般方法如下:
Step 1 :  Exponent = first byte - 193
Step 2 :  Subtract 1 from every other digit.
Step 3 :  Multiply each resulting digit by 100^(EXP - N), where EXP is the exponent value from step 1,N is the digit's placement order (for most significant digit N = 0)
Step 4 :  Add all the values.

    再看个小数的例子,再来加深理解这个算法:
17:28:18 SQL> select 0.567,dump(0.567),dump(0.567,16) from dual;
     0.567 DUMP(0.567)            DUMP(0.567,16)
---------- ---------------------- ---------------------
      .567 Typ=2 Len=3: 192,57,71 Typ=2 Len=3: c0,39,47
exponet : 192-193=-1
digit1  : (57-1)*power(100,-1)=0.56
digit2  : (71-1)*power(100,-2)=0.007
0.567=0.56+0.007=0.567

    那么负数的算法是什么样子的呢?请接着看。
17:29:53 SQL> select -1,dump(-1),dump(-1,16) from dual;
        -1 DUMP(-1)                DUMP(-1,16)
---------- ----------------------- ---------------------
        -1 Typ=2 Len=3: 62,100,102 Typ=2 Len=3: 3e,64,66
(1) 标志位62,根据(62<128)可以算到这个数字是负数,根据(62-62=0)可以算到科学表达式的指数是0,100的0次方,即power(100,0)。
(2) 数据位100,由于其是负数,oracle存储该数的方法是101减去该数值,即101-100=1。这样就得到了负数中1用0x64(100)表示,负数的99用0x02(2)表示。这样设计(用101减去该数值)是有原因的,正数+1到+99和负数-1到-99,只是一个符号的区别,而他们的大小关系却刚好相反。分别用0x02到0x100来表示+1到+99,那么就用0x100到0x02来表示-1到-99。
    即表示负数中的数值和它相反数的数据相加是0x66,也就是符号位。正数1用0x02表示,负数1用0x64表示,二者相加是0x66。负数多个一个标识位,用0x66表示。由于正数的表示范围是0x01到0x64,负数的表示范围是0x65到0x02。因此,不会在表示数字时出现的0x66表示,故在每个负数的后面要用上一个0x66(102)来表示负数的收尾,这样设计的原因是为了比较的简便。

    oracle比较两个数字的规则如下:
a、在没有超过21个字节的前提下,即数据是有效的前提下,从左往右标记,第一个byte为小的是数字较小的那个,如999.445<1000:
20:29:05 SQL> select 1000,dump(999.445) from dual;
      1000 DUMP(999.445)
---------- -----------------------------
      1000 Typ=2 Len=5: 194,10,100,45,51
20:29:19 SQL> select 1000,dump(1000) from dual;
      1000 DUMP(1000)
---------- -------------------
      1000 Typ=2 Len=2: 194,11

b、前面的都一样,如果有一个数字存储超过了21个字节,那么这个数字即是较小的那个数字。
c、如果2个数字存储都超过了21个字节,那么这2个数字认为是相等的。

    下面接着解释为什么要在负数的最后加上102这个byte来标记负数结束:
20:33:52 SQL> select -100,dump(-100),-115,dump(-115) from dual;
      -100 DUMP(-100)                    -115 DUMP(-115)
---------- ----------------------- ---------- --------------------------
      -100 Typ=2 Len=3: 61,100,102       -115 Typ=2 Len=4: 61,100,86,102

可见,如果不在后面加上102作为负数的首尾,那么-100(61,100),-115(61,100,86)就这样来表示,很显然就会得到-115>-100这样的错误的结果,因为-100(61,100)即-100(61,100,0),显然86>0。因此,要在要在最后加上102,那么102>86,故得到-100>-115这个正确的结果了。
(3)
17:25:12 SQL> select -123456789.123,dump(-123456789.123),dump(-123456789.123,16) from dual;
-123456789.123 DUMP(-123456789.123)                      DUMP(-123456789.123,16)
-------------- ----------------------------------------- --------------------------------------
    -123456789 Typ=2 Len=9: 58,100,78,56,34,12,89,71,102 Typ=2 Len=9: 3a,64,4e,38,22,c,59,47,66

它是如下计算的:
exponent指数底数=62-58=0x3e-0x3a=4,58-128<0可得到这个数字是负数。
digits:
digit1 : 101-100=0x65-0x64=1 > 1*100^4 = 1*power(100,4) = 1*power(100,4-0)=100000000
digit2 : 101-78 =0x65-0x4e=23>23*100^3 =23*power(100,3) =23*power(100,4-1)=23000000
digit3 : 101-56 =0x65-0x38=45>45*100^2 =45*power(100,2) =45*power(100,4-2)=450000
digit4 : 101-34 =0x65-0x22=67>67*100^1 =67*power(100,1) =67*power(100,4-3)=6700
digit5 : 101-12 =0x65-0x0c=89>89*100^0 =89*power(100,0) =89*power(100,4-4)=89
digit6 : 101-89 =0x65-0x59=12>12*100^-1=12*power(100,-1)=12*power(100,4-5)=0.12
digit7 : 101-71 =0x65-0x71=30>30*100^-2=30*power(100,-2)=30*power(100,4-6)=0.003
digit8 : 102可得到这是个负数
因此-123456789.123=-sum(100000000+23000000+450000+6700+89+0.12+0.0030)

    由此,得到计算负数的一般方法如下:
Step 1 :  Exponent = 62 - first byte
Step 2 :  Subtract every other digit from 101.
Step 3 :  Multiply each resulting digit by 100^(EXP - N), where EXP is the exponent value from step 1,N is the digit’s placement order (N starts at 0 for the most significant digit)
Step 4 :  Make sure to ignore the trailing byte if it is 0x66 (102).
Step 5 :  Add all the values.

    再看个小数的例子,来加深理解这个算法:
20:34:48 SQL> select -0.567,dump(-0.567) from dual;
    -0.567 DUMP(-0.567)
---------- -------------------------
     -.567 Typ=2 Len=4: 63,45,31,102
exponet : 62-63=-1
digit1  : (101-48)*power(100,-1)=0.56
digit2  : (101-31)*power(100,-2)=0.007
-0.567=-(0.56+0.007)=-0.567

    4、oracle的每个字节存储2位数,这个是很重要的一个特性。并且根据number类型最多占据21个字节,可以推断出number(p,s)类型中p的最大值只能是38=19*2了。

    5、根据Oracle的存储特性,还可以推出Oracle的number类型的取值范围。Oracle的concept上是这样描述的:The following numbers can be stored in a NUMBER column : Positive numbers in the range 1 x 10-130 to 9.99...9 x 10125 with up to 38 significant digits.Negative numbers from -1 x 10-130 to 9.99...99 x 10125 with up to 38 significant digits.Zero.
    下面来推导出正数的取值范围。最大值是所有的数据位都是99,而指数为是255-193=62,即正数的最大值是99*100^62=9.9*10^125。最小值是前19个数据位的数字都是0,最后一个数据位是0x01,而指数为193-68=65,故最小值是1*100^65=1*100^30。
    负数和正数在各使用了一半的编码,因此具有相同的极值范围。

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/17309626/viewspace-678560/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/17309626/viewspace-678560/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值