目錄
解剖SQLSERVER 第十四篇    Vardecimals 存储格式揭秘(译)
首頁 資料庫 mysql教程 解剖SQLSERVER 第十四篇 Vardecimals 存储格式揭秘(译)

解剖SQLSERVER 第十四篇 Vardecimals 存储格式揭秘(译)

Jun 07, 2016 pm 03:19 PM
sqlserver

解剖SQLSERVER 第十四篇 Vardecimals存储格式揭秘(译) http://improve.dk/how-are-vardecimals-stored/ 在这篇文章,我将深入研究vardecimals 是怎麽存储在磁盘上的。 作为一般的介绍vardecimals 是怎样的,什么时候应该使用,怎样使用,参考这篇文章 vard

解剖SQLSERVER 第十四篇    Vardecimals 存储格式揭秘(译)

http://improve.dk/how-are-vardecimals-stored/

在这篇文章,我将深入研究vardecimals 是怎麽存储在磁盘上的。

作为一般的介绍vardecimals 是怎样的,什么时候应该使用,怎样使用,参考这篇文章

 

vardecimal 存储格式启用了吗?

首先,我们需要看一下vardecimals 是否已经开启了,因为他会完全改变decimals 的存储方式。Vardecimal 不是独立的一种数据类型,所有使用decimals 的列都会使用vardecimals方式来存储并使用相同的system type(106)。注意在SQLSERVER里面,numeric 跟decimal是完全一样的。无论我在哪里我提到decimal, 你都可以使用numeric来替代并且会得到相同的结果

解剖SQLSERVER 第十四篇    Vardecimals 存储格式揭秘(译)

 


你可以执行下面语句来查看给定的表的vardecimal 是否开启

<span>SELECT</span> <span>OBJECTPROPERTY</span>(<span>OBJECT_ID</span>(<span>'</span><span>MyTable</span><span>'</span>), <span>'</span><span>TableHasVarDecimalStorageFormat</span><span>'</span>)
登入後複製

如果你没有权限运行上面语句,或者不想使用OBJECTPROPERTY函数,你可以查询sys.system_internals_partition_columns DMV 获取同样的信息。

<span>USE</span><span> test
</span><span>GO</span>

<span>SELECT</span>
    <span>COUNT</span>(<span>*</span><span>)
</span><span>FROM</span><span>
    sys.system_internals_partition_columns PC
</span><span>INNER</span> <span>JOIN</span><span>
    sys.partitions P </span><span>ON</span> P.partition_id <span>=</span><span> pc.partition_id
</span><span>INNER</span> <span>JOIN</span><span>
    sys.tables T </span><span>ON</span> T.<span>object_id</span> <span>=</span> P.<span>object_id</span>
<span>WHERE</span><span>
    T.name </span><span>=</span> <span>'</span><span>test_vardecimal</span><span>'</span> <span>AND</span><span>
    P.index_id </span><span> <span>1</span> <span>AND</span><span>
    PC.system_type_id </span><span>=</span> <span>106</span> <span>AND</span><span>
    PC.leaf_offset </span><span> <span>0</span></span></span>
登入後複製

 


固定长度变为可变长度
正常的decimal列在记录里面使用固定长度来存储。这意味着存储的是真正的数据。他不需要保存存储的字节数的长度信息这个长度信息用来计算并存储在元数据里。

一旦你打开vardecimals, 所有的decimals 不再使用固定长度来存储,进而用可变长度代替。

将decimal 作为可变长度字段来存储有一些特别含义
1、我们再也不能使用静态的方法来计算一个给定的值的所需字节数
2、会有两个字节的开销用来存储偏移值在可变长度偏移数组里
3、如果先前行记录没有可变长度列,那么开销实际上是4个字节因为我们也需要存储可变长度列的列数量
4、decimal 的实际值变成了可变数量的字节 这需要我们去解读

 

vardecimal 值由哪些部分组成
一旦我们开始解析行记录然后在可变长度部分检索vardecimal 的值,我们需要解析里面的数据。

正常的decimals 基本能存储一个巨大无比的整数(范围是根据元数据定义的decimal的位置)
vardecimals 的存储使用科学计数法。使用科学计数法,我们需要存储三个不同的值
1、符号(正数/负数)
2、指数
3、(对数的) 尾数

 

使用这三个组成部分,我们可以使用下面的公式计算出实际值

(<span>sign</span>) <span>*</span> mantissa <span>*</span> <span>10</span><span>sup<span>></span>exponent<span></span>sup<span>></span></span>
登入後複製

 

例子

假设我们有一个vardecimal(5,2)列,我们存储的值是123.45。在科学记数法里,将表示为1.2345 * 102
在这种情况下我们有正数符号(1),一个尾数1.2345和一个指数2。SQL Server知道尾数总是有一个固定小数点在第一个数字后面,正因为如此,这会简单的存储整数值12345作为尾数,

当指数是2,SQLSERVER知道我们的范围由指数2来定义,数值向右移动指数的长度,存储0作为实际的指数

 

一旦我们读取这个数,我使用下面的公式计算尾数(注意我们在这里不关心如果尾数是正的还是负的--我们会稍后将他保存到account里)

mantissa <span>/</span> <span>10</span><span>sup<span>></span><span>floor</span>(<span>log10</span>(mantissa))<span></span>sup<span>></span></span>
登入後複製

将我们的值带入进去,我们得到

<span>12345</span> <span>/</span> <span>10</span><span>sup<span>></span><span>floor</span>(<span>log10</span>(<span>12345</span>))<span></span>sup<span>></span></span>
登入後複製

通过简化我们得到

<span>12345</span> <span>/</span> <span>10</span><span>sup<span>></span><span>4</span><span></span>sup<span>></span></span>
登入後複製

使用科学计数法得到最终的尾数

<span>1.2345</span>
登入後複製

到目前为止一切顺利,我们现在得到尾数的值。在这里我们需要做两件事
1、加上符号位
2、根据指数值将decimal的小数点移动到右边正确的位置。当SQLSERVER知道范围是2,他将2替代4,并减去指数指定的范围--允许我们忽略范围并且只需要直接计算数字

因此我们获得了我们最终需要计算的最后数字

(sign) * mantissa * 10sup>exponentsup> => (1) * <span>1.2345</span> * 10sup>2sup> => <span>1.2345</span> * 10sup>2sup> = 123.45
登入後複製

 

 

读取符号位和指数
第一个字节包含符号和指数。在先前的例子里面,这些值占用4个字节(包含额外的2个字节的偏移数组)

<span>0xC21EDC20</span>
登入後複製

如果我们观察第一个字节,并且将他转换为binary,我们获得下面信息

解剖SQLSERVER 第十四篇    Vardecimals 存储格式揭秘(译)

解剖SQLSERVER 第十四篇    Vardecimals 存储格式揭秘(译)

最重要的那个位是最左边的那个位,或者从右开始数第7位(位置编号从0开始)那个位是符号位。如果设置为1表示正数,如果为0表示负数,我们看到是1表示正数

 

 

位0 - 6是一个7位的包含指数的值。一个常规的无符号7位值可以包含的范围是0~127。当decimal 数据类型需要表示一个范围 –1038+1到 1038-1,我们就需要存储负数。

我们可以使用7位的其中一位作为符号位,在其余的6位里存储值,允许的范围是–64 到 63。然而SQLSERVER会用尽7位去存储本身的数值,

不过存储的是一个偏移值64。因此,指数0会被存储为64 (64-64=0)。指数-1 会存储63 (63-64=-1),指数1会存储65 (65-64=1)如此类推。

在我们的例子里,读取位0-6 将会得到下面的值

解剖SQLSERVER 第十四篇    Vardecimals 存储格式揭秘(译)

66减去64这个偏移值就得出 指数2
66-64=2(指数)

 


尾数的chunk存储
余下的字节包含尾数值。我们把他转换为二进制

Hex: 1E       DC       <span>20</span><span>

Bin: </span><span>00011110</span> <span>11011100</span> <span>00100000</span>
登入後複製

尾数存储在10位的chunks里,每一个chunk显式尾数的3个数字(记住,尾数是一个很大的整数,直到后来我们开始把他作为一个十进制的指针数值 10的n次幂)

把这些字节切割放进去chunks里面 会得到如下的组别

解剖SQLSERVER 第十四篇    Vardecimals 存储格式揭秘(译)

 解剖SQLSERVER 第十四篇    Vardecimals 存储格式揭秘(译)

 

在这种情况下,一个字节8位 SQLSERVER在这里会浪费4位使用这种chunk大小的话。问题来了,为什么要选择一个chunk大小为10位?那10位 需要显示所有可能的三位整数(0-999)。

如果我们使用一个chunk的大小来表示一个数字会怎样?

在这种情况下,一个字节8位 SQLSERVER在这里会浪费4位使用这种chunk大小的话。问题来了,为什么要选择一个chunk大小为10位?那10位 需要显示所有可能的三位整数(0-999)。如果我们使用一个chunk的大小来表示一个数字会怎样?

刚才那样的情况,我们需要展示数值0-9.那总共需要4个位(0b1001 = 9)。然而,使用4个位的时候我们实际可以展示的最大范围是0-15(0b1111 = 15) --意味着我们浪费了6个值的空间(15-9=6)这些值永远不需要的。从百分比来讲,我们浪费了6/16=37.5%

 

让我们试着画出不同的chunk大小对应浪费的百分比的图:

解剖SQLSERVER 第十四篇    Vardecimals 存储格式揭秘(译)

我们看到chunk大小选择4和选择7 相比起选择chunk大小为10 有较大的浪费。
在chunk大小为20时,对于0浪费相当接近,但是他还是有2倍浪费率相比起10来说

 

现在,浪费不是最重要的。对于压缩来说,最理想的情况是对于绝对必要的数字我们不想使用多余的数字。在chunk大小为10的情况下,可以显示3个数字,我们浪费了2个数字空间范围是0-9。

然而,我们只关注范围100-999。如果我们的chunk大小选择20个位,每个chunk显示6个数字,我们浪费了了一些字节 值从0-99999,当我们只关注值1000000-999999。

基本上,这是一个折衷方案,浪费就越少 而且也越好。我们继续看图表,粒度越来越少。很明显 选择10个位作为chunk的大小是最好的选择 --这个选择的浪费是最小的 并且有合适的粒度大小 3个数字

 


在我们继续之前还有一些细节。想象一下我们需要存储的尾数值为4.12,有效的整数值是412

<span>Dec</span>: <span>412</span><span>
Bin: </span><span>01100111</span> <span>00</span><span>
Hex: </span><span>67</span>       <span>0</span>
登入後複製

在这种情况下,我们会浪费8位在第二个字节,因为我们只需要一个块,但我们需要两个字节来表示这10位。在这种情况下,鉴于过去两位不设置,SQL Server会截断最后一个字节。因此,如果你正在读一块,你的磁盘上,你可以假设其余部分不设置。


在这种情况下,我在第二个字节浪费8个位,因为我们只需要一个chunk,不过我们需要两个字节来显示这个位。在这种情况下,多余的两个位不会进行设置,SQLSERVER会简单的截断最后一个字节。因此,如果你读取一个chunk并且位数已经超出了磁盘的范围,你可以假设剩余的位并没有设置

解剖SQLSERVER 第十四篇    Vardecimals 存储格式揭秘(译)

 

解析一个vardecimal值
最后,我们准备去解析一个vardecimal 值(使用C#实现)!我们将使用先前的例子,存储123.45值使用decimal(5,2)列。在磁盘上,我们读取下面的字节数组根据调用的顺序

Hex: C2       1E       DC       <span>20</span><span>
Bin: </span><span>11000010</span> <span>00011110</span> <span>11011100</span> <span>00100000</span>
登入後複製

 解剖SQLSERVER 第十四篇    Vardecimals 存储格式揭秘(译)

读取符号位
读取符号位相对来说比较简单。我们将只需要在第一个字节上读取:

解剖SQLSERVER 第十四篇    Vardecimals 存储格式揭秘(译)

通过位运算符我们将右移7位,剩下的位是最重要的位。这意味着我们将得到值1 表示正数的符号位,如果是0表示负数

解剖SQLSERVER 第十四篇    Vardecimals 存储格式揭秘(译)

<span>decimal</span> sign = (value[<span>0</span>] >> <span>7</span>) == <span>1</span> ? <span>1</span> : -<span>1</span>;
登入後複製

 

 

读取指数
下面(技术上来讲这7个位是紧跟着符号位的)的7位包含了指数值

 解剖SQLSERVER 第十四篇    Vardecimals 存储格式揭秘(译)

 将十六进制值0b1000010 转换为进制值得到的结果是66.我们知道指数总是有偏移值64,我们需要将存储的值减去64从而得到实际值:

Exponent = 0b1000010 – 0n64  Exponent = <span>66</span> – <span>64</span> = <span>2</span>
登入後複製

 

 

读取尾数
接下来就是尾数值。前面提到,我们需要读取一个10个位的chunk,并且需要注意那些被截断的部分

首先,我们需要知道有多少有用的位。这样做很简单,我们只需简单的将尾数的字节数(除了第一个字节之外的所有字节)乘以8

<span>int</span> totalBits = (value.Length - <span>1</span>) * <span>8</span>;
登入後複製

一旦我们知道有多少位是可用的(在这个例子里 2个chunk 24位=3个字节* 8位),我们可以计算chunks的数目 

<span>int</span> mantissaChunks = (<span>int</span>)Math.Ceiling(totalBits / 10d);
登入後複製

因为每个块占用10位,我们只需要的比特总数除以10。如果有填充最后,匹配一个字节边界,它将是0的,不会改变最终的结果。因此为2字节尾数我们将有8位备用,将非标准都是0。

对于一个3字节尾数我们将有4位,再次添加0尾数总额。

因为每个chunk占用10个位,我们只需要除以将位的总数除以10。如果在结尾有占位,为了匹配位的边界,SQLSERVER会填充0但是这个不会影响上面公式得出的结果。

因此,对于一个2个字节的尾数我们会有8bit(这里作者是不是错了?应该是6bit吧) 是剩余的,这些剩余位都会被无意义的0填充。对于一个3字节 的尾数我们会有4bit 剩余,

再一次在总的尾数值上填充0

 

这里我们准备读取chunk的值。在读取之前,我们需要分配两个变量

<span>decimal</span> mantissa = <span>0</span><span>;
</span><span>int</span> bitPointer = <span>8</span>;
登入後複製

 

可变长的尾数值是由尾数值进行累加的,每次我们读取一个新10-bit chunk值就累加一次。bitPointer 是一个指针指向当前读取到的位。
我们不准备读取第一个字节,我们会从第8位开始读取(从0开始数,因此第8位 =第二个字节的第一位)

 

看一下这些位 他们可以简单的看成是一条long stream --我们只需要从左到右进行读取,对吧?不完全是,你可否记得,最右边的位是最重要的,
因此最右边的位应该是我们最先要读取的。然而,我们需要一次读取一个字节。同样,整体的方向是按chunk为单位的话是从左到右读取。

一旦我们达到chunk的位置,我们每次就需要一个字节一个字节地读取。bits1-8 在第一个字节里读取,bits9-10 在第二个字节里读取,

下面图片中橙色的箭头(最大的那个箭头指示字节读取顺序(chunkwise)从左向右,而每个独立的字节内部的读取顺序是小箭头那个 从右向左)

解剖SQLSERVER 第十四篇    Vardecimals 存储格式揭秘(译)

 

为了方便访问所有的bits,和避免做太多的人工的位移操作,我实例化一个BitArray 类 ,这个类包含了所有的数据位:

<span>var</span> mantissaBits = <span>new</span> BitArray(value);
登入後複製

使用这个类,你必须知道bit 数组如何跟字节进行映射。形象的描述,他会像下面那样,mantissaBits 数组指针在图片的上面:

解剖SQLSERVER 第十四篇    Vardecimals 存储格式揭秘(译)

 

解剖SQLSERVER 第十四篇    Vardecimals 存储格式揭秘(译)

 

我知道这看起来很复杂,不过所有这些复杂的事情只不过是需要知道指针的指向。我们的代码里面是字节数组。
我们访问每一个独立的bits的方式是通过mantissaBits 数组,这个数组只是一个很大的指向独立的bits的数组指针。

看一下第一个8 bits,manitssaBits 数组按照我们的读取方向很好地排列。第一个条目(mantissaBits[0])指向
第一个字节的最右一位。第二个条目指向第二个字节,以此类推。因此,第一个8 bits是直接读取的。然而,后面的两个,在manitssaBits 数组里面他们需要
我们跳过6个条目以至于我们读取条目14和15,他们指向下一个字节的最后两个bits(因为是从右向左读取的)。

 

读取第二个chunk,我们必须回去读取bit 条目8-13 然后跳到条目20-23,忽略条目16-19 因为他们只是不相关的占位padding。
这是相当棘手的。幸运的是我们可以自由选择从重要的位到不重要的位进行读取,或者相反

让我们看一下代码实现

<span>for</span> (<span>int</span> chunk = mantissaChunks; chunk > <span>0</span>; chunk--<span>)
{
    </span><span>//</span><span> The cumulative value for this 10-bit chunk</span>
    <span>decimal</span> chunkValue = <span>0</span><span>;

    </span><span>//</span><span> For each bit in the chunk, shift it into position, provided it's set</span>
    <span>for</span> (<span>int</span> chunkBit = <span>9</span>; chunkBit >= <span>0</span>; chunkBit--<span>)
    {
        </span><span>//</span><span> Since we're looping bytes left-to-right, but read bits right-to-left, we need
        </span><span>//</span><span> to transform the bit pointer into a relative index within the current byte.</span>
        <span>int</span> byteAwareBitPointer = bitPointer + <span>7</span> - bitPointer % <span>8</span> - (<span>7</span> - (<span>7</span> - bitPointer % <span>8</span><span>));

        </span><span>//</span><span> If the bit is set and it's available (SQL Server will truncate 0's), shift it into position</span>
        <span>if</span> (mantissaBits.Length > bitPointer &&<span> mantissaBits[byteAwareBitPointer])
            chunkValue </span>+= (<span>1</span>  chunkBit);

        bitPointer++<span>;
    }

    </span><span>//</span><span> Once all bits are in position, we need to raise the significance according to the chunk
    </span><span>//</span><span> position. First chunk is most significant, last is the least significant. Each chunk
    </span><span>//</span><span> defining three digits.</span>
    mantissa += chunkValue * (<span>decimal</span>)Math.Pow(<span>10</span>, (chunk - <span>1</span>) * <span>3</span><span>);
}</span>
登入後複製

外层循环遍历chunks,从最重要的到不重要的chunk。这里我们先遍历chunk2再遍历 chunk1

chunkValue 这个变量保存我们当前正在读取的chunk的所有值。我们会进行移位操作把位的值赋值给chunkValue (chunkValue += (1 直到已经解析完所有的10个bits

内层循环从最重要的bit到不重要的bit(就是说,在chunkbit里从条目9到条目0)。最右边的位开始倒序读取,我们避免跳过内部的单个字节。
我们在mantissaBits数组中总是从右到左读取bits,下面顶层的箭头就像这样:

解剖SQLSERVER 第十四篇    Vardecimals 存储格式揭秘(译)

虽然我们倒序读取每个字节里面的bit,其他的东西都是以自然方式读取,这让解析更加容易

byteAwareBitPointer 变量在我们的 mantissaBits数组里面是一个指针指示我们当前读取到的值。

这种计算确保我们读取每一个字节的时候都是从mantissaBits数组里最高位读取到最低位。下面是读取到的第一个chunk里面按mantissaBit 指针顺序的条目值

<span>7</span>, <span>6</span>, <span>5</span>, <span>4</span>, <span>3</span>, <span>2</span>, <span>1</span>, <span>0</span>, <span>15</span>, <span>14</span>
登入後複製

和第二个被读取到的chunk 按mantissaBit 指针顺序的条目值

<span>13</span>, <span>12</span>, <span>11</span>, <span>10</span>, <span>9</span>, <span>8</span>, <span>23</span>, <span>22</span>, <span>21</span>, <span>20</span>
登入後複製

解剖SQLSERVER 第十四篇    Vardecimals 存储格式揭秘(译)

一旦我们读取到特定的bit,我们进行移位把值保存到chunkValue 变量里--但只有这个位是可用的才会保存到chunkValue 变量(也就是说0不会保存并被截断)

一旦所有的bits都已经进行了移位,我们把chunk的值赋值给mantissa 。在我们的例子里,存储的值是12345,我们实际上
存储的值是123450(因为每一个chunk存储三个数字,这总是3的倍数)。第一个读取到的chunk(chunk2)包含值123 对应值是123000。
第二个读取到的chunk(chunk1)包含的值是450。乘以10(chunk–1)3


确保我们获取到正确的数量级(1000 for chunk 2 and x1 for chunk 1*)。对于每次的chunk循环,我们会添加最后的chunk值到尾数(mantissa )里最后进行相加

mantissa = mantissa / <span>10</span><sup>floor(log10(mantissa))</sup>
登入後複製

实现代码如下:

mantissa = mantissa / (<span>decimal</span>)Math.Pow(<span>10</span>, Math.Floor(Math.Log10((<span>double</span>)mantissa)));
登入後複製

尾数的结果值是1.2345

 

 

尾数的解析性能
这个实现的性能谈不上快,在你打击我之前有些说话我需要说。首先,我们可以容易地将整组bits一次性进行移位而不是每次移位。
确保每个chunk不需要两次或以上的移位操作,在这个实现里面使用了10次移位操作(因为每个chunk是10个bits)。
然而,我的目的是这个代码实现是为了代码的清晰和可读性。我的目的不是为了对OrcaMDF进行速度的优化

 

将这些东西全部结合起来
一旦我们有了符号位,指数,尾数,我们可以简单的计算最后的值就像这样:

<span>return</span> sign * mantissa * (<span>decimal</span>)Math.Pow(<span>10</span>, exponent);
登入後複製

在我们的例子里,结果将是这样:

1 * <span>1.2345</span> * 102 = <span>1.2345</span> * 100 = 123.45
登入後複製

 

 

结论

Vardecimal 是SQL2005 SP2唯一的压缩功能。到了SQL2008我们才有 行压缩和页压缩功能(这两个特性是vardecimal存储格式的超集)。
从那时起, 随着行压缩和页压缩的发布,微软已经把vardecimal 作为废弃功能,并且在将来的版本会删除这个功能。
因为Vardecimal 需要企业版才能使用,而行压缩和页压缩也是需要企业版,我们没有理由去使用Vardecimal 功能,除非你使用的是SQL2005
或者除非你有一个非常特别的数据集除了使用decimals压缩功能没有其他的功能能带来性能提升。

知道vardecimal 的存储格式是如何工作的对于研究压缩的内部是有好处的--压缩内部我将会写一篇文章 并且我将会在2012年春季的SQL Server Connections里进行演示

同时你可以github上在签出我的SqlDecimal.cs 实现。或者你可以看一下OrcaMDF 代码里的完整实现

<span>using</span><span> System;
</span><span>using</span><span> System.Collections;
</span><span>namespace</span><span> OrcaMDF.Core.Engine.SqlTypes
{
</span><span>public</span> <span>class</span><span> SqlDecimal : ISqlType
{
</span><span>private</span> <span>readonly</span> <span>byte</span><span> precision;
</span><span>private</span> <span>readonly</span> <span>byte</span><span> scale;
</span><span>private</span> <span>readonly</span> <span>bool</span><span> isVariableLength;
</span><span>public</span> SqlDecimal(<span>byte</span> precision, <span>byte</span><span> scale)
: </span><span>this</span>(precision, scale, <span>false</span><span>)
{ }
</span><span>public</span> SqlDecimal(<span>byte</span> precision, <span>byte</span> scale, <span>bool</span><span> isVariableLength)
{
</span><span>this</span>.precision =<span> precision;
</span><span>this</span>.scale =<span> scale;
</span><span>this</span>.isVariableLength =<span> isVariableLength;
}
</span><span>public</span> <span>bool</span><span> IsVariableLength
{
</span><span>get</span> { <span>return</span><span> isVariableLength; }
}
</span><span>public</span> <span>short</span>?<span> FixedLength
{
</span><span>get</span> { <span>return</span> Convert.ToInt16(<span>1</span> + getNumberOfRequiredStorageInts() * <span>4</span><span>); }
}
</span><span>private</span> <span>byte</span><span> getNumberOfRequiredStorageInts()
{
</span><span>if</span> (precision 9<span>)
</span><span>return</span> <span>1</span><span>;
</span><span>if</span> (precision 19<span>)
</span><span>return</span> <span>2</span><span>;
</span><span>if</span> (precision 28<span>)
</span><span>return</span> <span>3</span><span>;
</span><span>return</span> <span>4</span><span>;
}
</span><span>public</span> <span>object</span> GetValue(<span>byte</span><span>[] value)
{
</span><span>if</span>(!<span>isVariableLength)
{
</span><span>if</span> (value.Length !=<span> FixedLength.Value)
</span><span>throw</span> <span>new</span> ArgumentException(<span>"</span><span>Invalid value length: </span><span>"</span> +<span> value.Length);
</span><span>int</span>[] ints = <span>new</span> <span>int</span>[<span>4</span><span>];
</span><span>for</span> (<span>int</span> i = <span>0</span>; i )
ints[i] = BitConverter.ToInt32(value, <span>1</span> + i * <span>4</span><span>);
</span><span>var</span> sqlDecimal = <span>new</span> System.Data.SqlTypes.SqlDecimal(precision, scale, Convert.ToBoolean(value[<span>0</span><span>]), ints);
</span><span>//</span><span> This will fail for any SQL Server decimal values exceeding the max value of a C# decimal.
</span><span>//</span><span> Might want to return raw bytes at some point, though it's ugly.</span>
<span>return</span><span> sqlDecimal.Value;
}
</span><span>else</span><span>
{
</span><span>//</span><span> Zero values are simply stored as a 0-length variable length field</span>
<span>if</span> (value.Length == <span>0</span><span>)
</span><span>return</span><span> 0m;
</span><span>//</span><span> Sign is stored in the first bit of the first byte</span>
<span>decimal</span> sign = (value[<span>0</span>] >> <span>7</span>) == <span>1</span> ? <span>1</span> : -<span>1</span><span>;
</span><span>//</span><span> Exponent is stored in the remaining 7 bytes of the first byte. As it's biased by 64 (ensuring we won't
</span><span>//</span><span> have to deal with negative numbers) we need to subtract the bias to get the real exponent value.</span>
<span>byte</span> exponent = (<span>byte</span>)((value[<span>0</span>] & <span>127</span>) - <span>64</span><span>);
</span><span>//</span><span> Mantissa is stored in the remaining bytes, in chunks of 10 bits</span>
<span>int</span> totalBits = (value.Length - <span>1</span>) * <span>8</span><span>;
</span><span>int</span> mantissaChunks = Math.Max(totalBits / <span>10</span>, <span>1</span><span>);
</span><span>var</span> mantissaBits = <span>new</span><span> BitArray(value);
</span><span>//</span><span> Loop each chunk, adding the value to the total mantissa value</span>
<span>decimal</span> mantissa = <span>0</span><span>;
</span><span>int</span> bitPointer = <span>8</span><span>;
</span><span>for</span> (<span>int</span> chunk = mantissaChunks; chunk > <span>0</span>; chunk--<span>)
{
</span><span>//</span><span> The cumulative value for this 10-bit chunk</span>
<span>decimal</span> chunkValue = <span>0</span><span>;
</span><span>//</span><span> For each bit in the chunk, shift it into position, provided it's set</span>
<span>for</span> (<span>int</span> chunkBit = <span>9</span>; chunkBit >= <span>0</span>; chunkBit--<span>)
{
</span><span>//</span><span> Since we're looping bytes left-to-right, but read bits right-to-left, we need
</span><span>//</span><span> to transform the bit pointer into a relative index within the current byte.</span>
<span>int</span> byteAwareBitPointer = bitPointer + <span>7</span> - bitPointer % <span>8</span> - (<span>7</span> - (<span>7</span> - bitPointer % <span>8</span><span>));
</span><span>//</span><span> If the bit is set and it's available (SQL Server will truncate 0's), shift it into position</span>
<span>if</span> (mantissaBits.Length > bitPointer &&<span> mantissaBits[byteAwareBitPointer])
chunkValue </span>+= (<span>1</span>  chunkBit);
bitPointer++<span>;
}
</span><span>//</span><span> Once all bits are in position, we need to raise the significance according to the chunk
</span><span>//</span><span> position. First chunk is most significant, last is the least significant. Each chunk
</span><span>//</span><span> defining three digits.</span>
mantissa += chunkValue * (<span>decimal</span>)Math.Pow(<span>10</span>, (chunk - <span>1</span>) * <span>3</span><span>);
}
</span><span>//</span><span> Mantissa has hardcoded decimal place after first digit</span>
mantissa = mantissa / (<span>decimal</span>)Math.Pow(<span>10</span>, Math.Floor(Math.Log10((<span>double</span><span>)mantissa)));
</span><span>//</span><span> Apply sign and multiply by the exponent</span>
<span>return</span> sign * mantissa * (<span>decimal</span>)Math.Pow(<span>10</span><span>, exponent);
}
}
}
}</span>
登入後複製

 

可能大家不是很明白尾数的计算方式

尾数部分:00011110 11|011100 0010|0000

字节1:00011110
字节2:11011100
字节3:00100000
chunk分隔线:|

chunk2:00011110 11 转换为十进制 123  然后再乘以10的3次方  123 * 103  =123000
chunk1:011100 0010 转换为十进制 450  然后再乘以10的1次方  450 * 101  =450

chunk2+chunk1=123450

解剖SQLSERVER 第十四篇    Vardecimals 存储格式揭秘(译)

解剖SQLSERVER 第十四篇    Vardecimals 存储格式揭秘(译)

 

解剖SQLSERVER 第十四篇    Vardecimals 存储格式揭秘(译)

 

第十四篇完

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

<🎜>:泡泡膠模擬器無窮大 - 如何獲取和使用皇家鑰匙
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
北端:融合系統,解釋
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
Mandragora:巫婆樹的耳語 - 如何解鎖抓鉤
3 週前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

熱門話題

Java教學
1666
14
CakePHP 教程
1425
52
Laravel 教程
1323
25
PHP教程
1272
29
C# 教程
1251
24
sqlserver怎麼匯入mdf文件 sqlserver怎麼匯入mdf文件 Apr 08, 2024 am 11:41 AM

匯入步驟如下:將 MDF 檔案複製到 SQL Server 的資料目錄(通常為 C:\Program Files\Microsoft SQL Server\MSSQL\DATA)。在 SQL Server Management Studio(SSMS)中,開啟資料庫並選擇「附加」。點選“新增”按鈕,選擇 MDF 檔案。確認資料庫名稱,點選確定按鈕即可。

sqlserver資料庫中已存在名為的物件怎麼解決 sqlserver資料庫中已存在名為的物件怎麼解決 Apr 05, 2024 pm 09:42 PM

對於 SQL Server 資料庫中已存在同名對象,需要採取下列步驟:確認物件類型(表格、檢視、預存程序)。如果物件為空,可使用 IF NOT EXISTS 跳過建立。如果物件有數據,使用不同名稱或修改結構。使用 DROP 刪除現有物件(謹慎操作,建議備份)。檢查架構更改,確保沒有引用刪除或重新命名的物件。

怎麼查看sqlserver連接埠號 怎麼查看sqlserver連接埠號 Apr 05, 2024 pm 09:57 PM

若要查看 SQL Server 連接埠號碼:開啟 SSMS,連線到伺服器。在物件資源管理器中找到伺服器名稱,右鍵單擊它,然後選擇“屬性”。在「連線」標籤中,查看「TCP 連接埠」欄位。

sqlserver誤刪資料庫怎麼恢復 sqlserver誤刪資料庫怎麼恢復 Apr 05, 2024 pm 10:39 PM

若誤刪 SQL Server 資料庫,可採取下列步驟還原:停止資料庫活動;備份日誌檔案;檢查資料庫日誌;復原選項:從備份還原;從交易日誌還原;使用 DBCC CHECKDB;使用第三方工具。請定期備份資料庫並啟用交易日誌以防止資料遺失。

sqlserver資料庫在哪裡 sqlserver資料庫在哪裡 Apr 05, 2024 pm 08:21 PM

SQL Server 資料庫檔案通常儲存在下列預設位置:Windows: C:\Program Files\Microsoft SQL Server\MSSQL\DATALinux: /var/opt/mssql/data可透過修改資料庫檔案路徑設定來自訂資料庫檔案位置。

sqlserver服務無法啟動怎麼辦 sqlserver服務無法啟動怎麼辦 Apr 05, 2024 pm 10:00 PM

當 SQL Server 服務無法啟動時,可採取下列步驟解決:檢查錯誤日誌以確定根本原因。確保服務帳戶具有啟動服務的權限。檢查依賴項服務是否正在執行。禁用防毒軟體。修復 SQL Server 安裝。如果修復不起作用,重新安裝 SQL Server。

Java連接SqlServer錯誤如何解決 Java連接SqlServer錯誤如何解決 May 01, 2023 am 09:22 AM

問題發現這次使用的是SqlServer資料庫,之前並沒有使用過,但是問題不大,我按照需求文檔的步驟連接好SqlServer之後,啟動SpringBoot項目,發現了一個報錯,如下:剛開始我以為是SqlServer連接問題呢,於是便去查看資料庫,發現資料庫一切正常,我首先第一時間問了我的同事,他們是否有這樣的問題,發現他們並沒有,於是我便開始了我最拿手的環節,面向百度程式設計.開始解決具體報錯資訊是這樣,於是我便開始了百度報錯:ERRORc.a.d.p.DruidDataSource$CreateCo

sqlserver安裝失敗怎麼樣刪除乾淨 sqlserver安裝失敗怎麼樣刪除乾淨 Apr 05, 2024 pm 11:27 PM

如果 SQL Server 安裝失敗,可透過下列步驟清理:解除安裝 SQL Server刪除註冊表項刪除檔案和資料夾重新啟動計算機

See all articles