分类目录归档:oracle

Oracle number类型分析

Oracle number类型分析

Oracle使用number类型表示定点数,使用两个参数来确定number的位数和小数部分精度,如number(p, s),其中p表示precision,s表示scale。

scale是小数位数的限制:s大于零时,表示精度限制在小数点后s位,超过s位后面的小数将被四舍五入;s小于等于0时,小数部分被舍去,且小数点前-s位,将被四舍五入,即小于10的-s次方的数将被四舍五入为零。

precision与scale的差值是整数位数的限制:p-s大于零时,表示整数部分的位数不能超过p-s,否则将报错;p-s小于等于零时,表示整数部分必须为0,小数点后-(p-s)位也必须为0,否则将报错。

举例说明如下:

number(3, 2) 1.2345 ==> 1.23 小数部分四舍五入保留2位

12.3 ==> Error 整数部分大于1位

number(3, -2) 345.6 ==> 300 小数点前2位被四舍五入

45.6 ==> 0 小数点前2位被四舍五入

123456.7 ==> Error 整数部分大于5位

number(2, 3) 1.2 ==> Error 整数部分不为0

0.1 ==> Error 小数部分前1位不为0

0.02345 ==> 0.023 小数部分超过3位后面的被四舍五入

Number的内存格式,以最优的比较计算速度为设计原则,对于不同精度不同大小的数值使用变长方式存储,做到能够使用memcmp直接进行数值大小的比较。因此设计number存储格式如下图所示:

其中:

1. length表示了其后面byte数组的长度;

2. 符号位(sign bit)和指数位(exponent)一起存储在byte数组的第一个元素:

(1) 其中sign bit为1表示非负数,sign bit为0表示负数,这样保证了在比较第一个byte时就能够比较出定点数值负数小于非负数;

(2) 指数位使用类似的编码方式(后面详细说明),保证了在符号位相同的情况下,指数位的大小直接反映了定点数值的大小;

3. 假设底数为B,后面每个byte存储一个大小为[0, B-1]的整数,做到在符号位和指数位都想同的情况下,使用memcmp依次比较每个digit即可反映数值的比较结果

考虑内存格式利用率和容易理解的折中,我们设底数B为100,每个digit保存0~99,使每个digit乘以100的n次方后,相加后即是最终的定点数值,举例如下(当然底数也可以是128或其他值,2整数次方的底数可能在计算时有更快的位运算方式,但是其内存格式不直观不易理解,且对小数的表示很难做到直接的memcmp比较,因此没有采用):

31415.009=3*100^2 + 14*100^1 + 15*100^0 + 0*100^-1 + 9*100^-2

 

指数位的设计,在第一个byte中,最高位用于存储sign bit,可以使用剩下的7个bit存储指数位,对于正数来说,使用0x80~0xFF总共可以表示128个指数值,因此使用第二个bit保存指数位的符号,非负指数符号位为1,负指数符号位为0,使得非负指数天然大于负指数。即[0x80, 0xbf]表示负指数,[0xc0, 0xff]表示非负指数,选择0xc0作为正负指数的分界点,减去0xc0,即为指数值。

但是考虑到oracle中定点数正数的表示范围是1 x 10-130 to 9.99…9 x 10125,要表示1e-130,指数值最小要到-65(编码为128),因此调整分界点为0xc1(128-x=-65,x=193),保证小数的表示范围满足需求。

如123.0,将被存储为(示例仅仅为演示指数位的设计,非最终设计):

len=3, 0xc2, 1, 23

还原定点数值的算法为:

123.0 = 1*100^(0xc2-0xc1) + 23*100^(0xc2-0xc1-1)

再比如0.123,将被存储为(示例仅仅为演示指数位的设计,非最终设计):

len=3, 0xc0, 12, 30

还原定点数值的算法为:

0.123 = 12*100^(0xc0-0xc1) + 30*100^(0xc0-0xc1-1)

而对于负数来说,0x00~0x7f总共可以表示128个指数值,与正数不同的是:指数值越大,实际定点数值约小。因此使用第二个bit保存指数位的符号,但是非负指数符号位为0,负指数符号位为1。即[0x00, 0x3f]表示非负指数,[0x40, 0x7f]表示负指数,选择0x40作为正负指数的分界点,被0x40减,即为指数值。

但是考虑到oracle中定点数负数的表示范围是-1 x 10-130 to -9.99…9 x 10125,要表示-1e-130,指数值最小要到-65(编码为127),因此调整分界点为0x3e(x-127=-65,x=62),保证小数的表示范围满足需求。

如-123.0,将被存储为(示例仅仅为演示指数位的设计,非最终设计):

len=3, 0x3d, 1, 23

还原定点数值的算法为:

-123.0 = -1*100^(0x3e-0x3d) – 23*100^(0x3e-0x3d-1)

再比如-0.123,将被存储为(示例仅仅为演示指数位的设计,非最终设计):

len=3, 0x3f, 12, 30

还原定点数值的算法为:

-0.123 = -12*100^(0x3e-0x3f) – 30*100^(0x3e-0x3f-1)

 

digit的设计,从第二个byte开始依次存储,主要考虑 符号位和指数位相同时如何实现memcmp比较。因为在符号位不同的情况下,可以自然的比较出非负数大于负数;而在符号位相同的情况下,指数位也可以直接反映出定点数的大小。所以对于digit的设计,只需要考虑符号位和指数位都相同的情况,因此只需要分别考虑定点数值为正数,负数和0这三种情况。正数的情况比较简单,digit保存[0, 99],直接按byte比较即可反映定点数值大小;负数的情况比较特殊,因为-1>-99,所以1在byte上编码后要大于99,我们使用100减去digit作为编码后的值,如1被编码为99,99被编码为1。举例如下:

-10023.0,将被存储为(示例仅仅为演示指数位的设计,非最终设计):

len=4, 0x3c, 99, 100, 77

还原定点数值的算法为:

-10023.0 = -(100-99)*100^(0x3e-0x3c)

                  – (100-100)*100^(0x3e-0x3c-1)

                  – (100-77)*100^(0x3e-0x3c-2)

但是这个编码存在一个问题,考虑-10023.0和-10023.1,在上面描述的存储格式中,使用memcmp比较,-10023.0将小于-10023.1,原因是与正数不同,在定点数值为负的情况下,后面越多的小数位,将使得定点数值越小。因此在定点数值为负的情况下,需要增加一个byte结束符,保证这个结束符大于有效的digit,而digit有效值范围是0~100,因此使用101作为定点数值为负情况下的结束符。

-10023.0,存储格式如下(示例仅仅为演示指数位的设计,非最终设计):

len=5, 0x3c, 99, 100, 77, 101

而oracle还考虑到了’\0’存储可能造成字符串结束符歧义的问题,因此digit存储的数字范围整体+1,即对正数用1~100表示[0, 99],对负数用2~101表示[99, 0],避开了存储’\0’。同时结束符也要修正为102。

10023.0,最终存储格式如下:

len=4, 0xc3, 2, 1, 24

-10023.0,最终存储格式如下:

len=5, 0x3c, 101, 102, 78, 102

 

0值digit的舍去,根据上面多次示例的定点数值还原算法,可以看到digit为0的情况下对计算结果没有影响,可以考虑舍去以节省空间,但是位于digit数组中间的0不能舍去,因为还原算法需要遍历数组并依次为每个digit计算对应的指数值。因此对于定点数值非负的情况将digit数组末尾的0舍去,而对于定点数值为负的情况则将结束符101前的100舍去,距离如下:

10000,最终的存储格式如下:

len=2, 0xc3, 1

-10000,最终的存储格式如下:

len=3, 0x3c, 99, 101

 

定点数值为0的特殊处理,使用非负数中最小的:len=1, 0x80作为0的编码。

 

定点数的表示范围,oracle中对定点数的范围限制:

  • 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

sign bit,exponent和digit组成的byte数组。其中一个byte用来保存符号位和整数,在处理负数时额外用1个byte保存结束符。因此对于正数最多使用20byte来保存,对于负数最多使用21byte来保存。因此byte数组中最多有19个byte可以用于保存定点数的有效位,即精度(precison)范围为[1, 38]。

而oracle规定的刻度(scale)范围[-84, 127],限制了定点数值上限为9.99…9 x 10121 与不设定scale情况下number类型的上限9.99…9 x 10125 相差4个数量级,但是暂时不清楚oracle这个限制的原因。

Loading