65.9K
CodeProject 正在变化。 阅读更多。
Home

多项式除法的数据结构

starIconstarIconstarIconstarIconstarIcon

5.00/5 (5投票s)

2015 年 5 月 22 日

CPOL

13分钟阅读

viewsIcon

17235

downloadIcon

201

本文在两个应用场景下,使用熟悉的長整數除法算法来实现多项式除法。

本文在两个应用场景下,使用熟悉的長整數除法算法来实现多项式除法。第一个应用涉及哈希表索引的计算,第二个应用涉及循环冗余校验码(CRC)的计算。在简要描述这两个应用之后,将描述用于多项式除法的数据结构。

多项式除法散列

一种基于代数编码理论的散列技术使用多项式除法来计算哈希表的索引(参见 Knuth, D. The Art of Computer Programming, Vol. 3: Sorting and Searching. Boston, Massachusetts: Addison Wesley, 1973, pp. 512-513; Second Edition, 1998, p. 520)。假设表的大小 \(m\) 是 2 的幂,即 ,对于某个 \(m > 1\)。为了将键散列到表中,该技术使用一个 m 次多项式

$ P(x) = x^m + p_{m-1}x^{m-1} + ... p_1x + p_0 $

\(x = 2\) 时,它可以被解释为整数的二进制表示。

要散列的键被假定为一个 \(q\) 个字符的字符串。字符串的 ASCII 码构成一串比特,这无非是一个 \(n\) 比特(\(n = 8q\))的二进制数。这样的数字也可以被解释为一个关于 \(x\) 的多项式

$ K(x) = k_{n-1}x^{n-1} + ... + k_1x + k_0 $

哈希表中的哈希索引是通过多项式除法 \(x^mK(x) \pmod{P(x)}\) 的余数获得的,其中使用模 2 算术。乘法 \(x^mK(x)\) 等同于将键的比特向左移动 \(m\) 位,除法的余数是多项式

$ H(x)=h_{m-1}x^{m-1} + ... + h_1x+h_0 $

它表示 \(m\) 比特的二进制数 \(h_{m-1}...h_1h_0\)。这个数字是表的正确索引,因为对于大小为 \(2^m\) 的表,索引范围从 0 到 \(2^m-1\),每个索引都用 \(m\) 个比特进行二进制编码。

作为说明,考虑一个二进制编码为 110100110111 的键。该二进制数的多项式表示为

$ M(x)=x^{11}+x^{10}+x^8+x^5+x^4+x^2+1 $

现在假设这个键要被索引到一个大小为 \(2^5\)(32 个元素,索引范围由 5 比特的数字 00000 到 11111 跨越)的哈希表中,并且除数多项式选择为

$ G(x)=x^5+x^4+x^2+1 $

在模运算中,我们不关心商,只关心長除法 \(x^mK(x) \pmod{P(x)}\) 的余数。因此,除法使用 \(\oplus \) 进行减法(不借位),如下所示

11010011011100000
110101
------
00000111011100000
     110101
     ------
     001110100000
       110101
       ------
       0011110000
         110101
         ------
         00100100
           110101
           ------
           010001

余数为 10001,其整数等价物(17)是该键在哈希表中的索引。

散列中最难解决的问题是避免冲突,而明智地选择散列函数至关重要。在多项式除法散列的情况下,除数多项式决定了该方法的有效性。Knuth 指出,对于由 15 个比特组成的键,10 次的除数多项式可以为任意两个相差少于七个比特位置的键产生不同的余数。

循环冗余校验 (CRC) 码的计算

CRC 码用于计算机之间通过通信线路的数据传输,以及硬盘和 CPU 之间的数据传输,以检测突发错误(参见 Ercegovac, M.D., and T. Lang Digital Systems and Hardware/ Firmware Algorithms. New York: Wiley, 1985, pp. 744-745)。“突发”定义为高速传输的大块数据。

CRC 码由 \(k\) 个数据比特(或消息比特)和 \(r < k\) 个校验比特组成。校验比特是通过将一个 *数据比特向量* 除以一个 *生成器比特向量* 的余数得到的。为了降低除法运算的复杂性,减法是以按位方式进行的,不借位。因此,如果 \(X=x_{n-1}x_{n-2}...x_1x_0\)\(Y=y_{n-1}y_{n-2}...y_1y_0\) 表示两个长度为 \(n\) 的比特向量,则减法 \(X-Y=D=d_{n-1}d_{n-2}...d_1d_0\) 按位进行,其中 \(d_i=x_i\oplus y_i\),对于 \(0\leq i< n\)

由于二进制数的比特等于 2 的多项式的系数,因此一个 \(k\) 比特的消息可以被看作是一个 \(k-1\) 次的多项式 \(M(x)\)。类似地,为了得到一个 \(r\) 比特的 CRC 校验向量,可以使用一个 \(r\) 次的多项式 \(G(x)\) 作为生成器。具体来说,消息多项式 \(M(x)\) 除以生成器多项式 \(G(x)\),得到

$ \frac{x^yM(x)}{G(x)}=Q(x)\oplus \frac{R(x)}{G(x)} $

其中 \(Q(x)\) 是商多项式,\(R(x)\) 是通过类似于長整數除法的过程获得的余数多项式。商多项式被丢弃,余数多项式就是消息的 CRC 码。为了数据传输的目的,通过将 CRC 码附加到消息后面来形成一个 *编码消息*。编码消息也可以看作是关于 \(x\) 的多项式,定义为

$ C(x)=x^yM(x)\oplus R(x) $

数据传输中的错误效果形式化描述如下。令 \(E(x)\) 表示错误多项式,它是一个比特向量,在消息中被错误修改(翻转)的比特位置为 1,在未更改的比特位置为 0。错误会将编码消息修改为形式为

$ C_{error}=C(x)\oplus E(x) $

这个 *有错误的编码消息* 模拟了通信线路上的噪声影响,并代表了接收到的消息。

为了验证传输错误是否发生,将接收到的消息除以相同的生成器多项式 \(G(x)\)。数学上执行该操作,

$ \frac{C_{error}(x)}{G(x)}=\frac{x^yM(x)\oplus R(x)\oplus E(x)}{G(x)}=Q(x)\oplus \frac{E(x)}{G(x)} $

最终结果是由于异或算子自身的抵消性质。由于商多项式无关紧要,如果除法 \(E(x) / G(x)\) 的余数不等于零比特向量,即 \(E(x)\) 不能被 \(G(x)\) 整除,则会检测到传输错误。生成器多项式的选择是为了检测所有长度 *小于* 该多项式次数的突发错误。常见的生成器多项式次数为 16 或 32。

作为说明,考虑多项式除法散列示例的一个变体。一个系统要处理 12 比特的消息,生成器多项式的次数为 5 (CRC-5),定义为

$ G(x)=x^5+x^4+x^2+1 $

要传输的消息是 110100110111,其多项式表示为

$ M(x)=x^{11}+x^{10}+x^8+x^5+x^4+x^2+x+1 $

消息的 CRC 码是通过長除法计算的,使用 \(\oplus \) 进行不借位的减法

11010011011100000
110101
------
00000111011100000
     110101
     ------
     001110100000
       110101
       ------
       0011110000
         110101
         ------
         00100100
           110101
           ------
           010001

得到余数 10001。因此,编码消息将是 11010011011110001。

现在假设传输错误改变了编码消息中 *三个* 连续的比特,如错误向量 00001110000000000 所描述。得到的错误编码消息是 11011101011110001,除以生成器多项式得到非零余数,因此检测到了错误。另一方面,如果发生错误,如 00101111100000000,改变了五个或更多比特,则错误消息将是 11111100111110001,除以生成器多项式将得到零余数,结果是该错误将未被检测到。

八位移位寄存器

通过多项式除法计算哈希索引和 CRC 码是同一问题的两个方面。这两种计算通常在硬件中通过移位寄存器来实现,但是可以定义一组数据结构在软件中高效地实现多项式除法。第一步是定义一个类来建模(即模拟)基本移位寄存器的操作。考虑建模一个通用的、组合式的 8 位移位寄存器,它具有以下硬件框图和功能规范。

寄存器有八个并行输入比特(\(i_7...i_0\))、八个并行输出比特(\(o_7...o_0\))、一个串行输入比特(\(s_{in}\))、一个串行输出比特(\(s_{out}\))以及三个命令输入(*load*、*clear* 和 *shift left*)。如果激活 clear 信号,则比特 \(o_7...o_0\) 都设置为零。如果激活 load 信号,则比特 \(i_7...i_0\) 被复制到比特 \(o_7...o_0\)。如果激活 shift left 信号,寄存器将其比特向左移动一位:\(s_{out}\leftarrow o_7,o_{k+1}\leftarrow o_k\) (对于 \(0\leq k< 7\)),并且 \(o_0\leftarrow s_{in}\)\(s_{in}\)\(s_{out}\) 线允许将多个寄存器级联连接。输出比特 \(o_7...o_0\) 可以随时读取。

要在软件中建模硬件移位寄存器,可以定义一个 C# 类来将寄存器的比特表示为私有数据成员,并通过成员函数实现其操作。(此后,所有 C# 类都假定位于 PolynomialDivision 命名空间中,并且所有代码都假定在控制台应用程序的上下文中运行。)

   public class _8bitShiftRegister
   {
      private byte data; // register's data bits

      public _8bitShiftRegister()
      {
         data = 0x00;
      }

      public _8bitShiftRegister( byte _data )
      {
         data = _data;
      }

      public byte Data
      {
         get { return data; }
         set { data = value; }
      }

      public void Load( byte _data ) // "Load" command (signal)
      {                              // "parameterized" version of Data.set
         data = _data;
      }

      public byte Read() // "Read" command (signal)
      {                  // "parameterized" version of Data.get
         return data;
      }

      public void Clear() // "Clear" command (signal)
      {
         data = 0x00;
      }

      public byte ShiftLeft( byte SerialIn ) // "Shift left" command (signal)
      {
         byte SerialOut = (byte)( data >> 7 );

         data <<= 1; // shift left 1 bit
         if ( SerialIn != 0x00 )
         {
            data |= 0x01;
         }
         return SerialOut;
      }

      public void Divide( _8bitShiftRegister source ) // "Divide" command
      {
         data ^= source.Data; // modulo 2 division via bitwise xor operator
      }

      public void Print( NL nl = NL.no )
      {
         Console.Write( String.Format( "{0:x2}", data ) );
         if ( nl == NL.yes )
         {
            Console.WriteLine();
         }
      }

      public void SelfTest()
      {
         byte sIn = 0;
         int i;

         Console.WriteLine( "Self-test 1\n" );

         data = 0xff;
         Print( NL.yes );

         for ( i = 0; i < 8; ++i )
         {
            byte sOut;

            sOut = ShiftLeft( sIn );
            Console.WriteLine( 
               String.Format( "{0} <-- {1:x2} <-- {2}", sOut, data, sIn ) );
         }
         Console.WriteLine();

         Console.WriteLine( "Self-test 2\n" );

         sIn = 0x01
         data = 0x00;
         Print( NL.yes );

         for ( i = 0; i < 8; ++i )
         {
            byte sOut;

            sOut = ShiftLeft( sIn );
            Console.WriteLine( 
               String.Format( "{0} <-- {1:x2} <-- {2}", sOut, data, sIn ) );
         }
         Console.WriteLine();
      }
   }// _8bitShiftRegister (class)

_8bitShiftRegister 的成员函数 LoadReadClearShiftLeft 构成了对寄存器行为规范的直接实现。函数 Divide 的定义是为了在实现多项式除法时使用此基本数据结构。它通过按位异或算子实现了模 2 算术中的 8 位除法。类似地,函数 PrintSelfTest 不是寄存器规范的一部分,但对于仿真很方便。可以通过以下程序来行使类的自测试能力。

   class Program
   {
      static void Main( string[] args )
      {
         _8bitShiftRegister reg = new _8bitShiftRegister();

         reg.SelfTest();
      }
   }// Program (class)

执行此程序后,在命令提示符窗口中会产生以下输出(此处以两列格式显示)。

自测试 1
 
ff
1 <-- fe <-- 0
1 <-- fc <-- 0
1 <-- f8 <-- 0
1 <-- f0 <-- 0
1 <-- e0 <-- 0
1 <-- c0 <-- 0
1 <-- 80 <-- 0
1 <-- 00 <-- 0

自测试 2
 
00
0 <-- 01 <-- 1
0 <-- 03 <-- 1
0 <-- 07 <-- 1
0 <-- 0f <-- 1
0 <-- 1f <-- 1
0 <-- 3f <-- 1
0 <-- 7f <-- 1
0 <-- ff <-- 1

第一个测试将 0xff 加载到寄存器中,将串行输入设置为 0,然后左移八次。第二个测试将 0x00 加载到寄存器中,将串行输入设置为 1,然后左移八次。程序输出的格式是 serial-out <-- data-bits <-- serial-in。读者可以验证结果是否符合预期。

使用移位数组进行多项式除法

为了通过移位比特来实现多项式除法,_8bitShiftRegister 类可以用作 *移位数组* 的基本数据结构。在进行長整數除法时,手算时是将除数在被除数下方移位,而在软件中,可以将被除数在除数上方移位。如果被除数存储在 8 位移位寄存器数组中,那么对数组进行左移操作是一项简单的任务。

为了能够用除数除被除数的某个前缀,必须有一种方法通过名称引用移位数组的某个部分以便对其进行显式操作,这可以通过 C/C++ 的 union 来命名移位数组的前缀来实现。(此时 mArgmChars 的值无关紧要。)

      union {
         _8bitShiftRegister arg[ mArg ];            // argument to generator polynomial
         _8bitShiftRegister chars[ mChars + mArg    // character buffer
                                          + mArg ]; // + area to append zeros
      } AC;

C# 没有 union 数据类型。但是,System.Runtime.InteropServices 命名空间提供了使用 struct 来模拟 unions 的功能,如下所示。

   [StructLayout( LayoutKind.Explicit, Size = 8 )]
   struct Union
   {
      [FieldOffset( 0 )]
      public _8bitShiftRegister[] arg;   // argument to generator polynomial
      [FieldOffset( 0 )]
      public _8bitShiftRegister[] chars; // character buffer (dividend) + area to append zeros
   }// Union

这些信息是从 James Kovacs 在 Microsoft Developer Network 的网页上获得的。(在 Google 中使用搜索字符串“Union in C#”,第一个标题为“C# equivalent to C union”的链接指向他的文章。)重要的是要认识到这里定义的 Union 是一个 *类型*,而不是一个变量。

假设要操作的多项式的最大次数为 31,因此需要 32 位表示,并且被除数始终由 8 的倍数个比特表示。此后,所有变量和函数将分别是类 ShiftArray 的数据成员和方法。(完整代码在文件 ShiftArray.cs 中)。数据成员的定义以及注释解释如下。

      private _8bitShiftRegister[] poly, // divisor polynomial
                                   mask; // mask used by function ShiftLeftToMSb

      private Union AC;

      private uint uiPoly,  // uint equivalent of poly
                   uiMask;  // uint equivalent of mask

      private uint[] uiDmask; // masks used to print a polynomial

      private int nBits,    // # of bits in AC.chars
                  nBytes,   // # of bytes in AC.chars == ceiling( nBits / 8 )
                  cBits,    // current # of bits in AC.chars
                  cBytes,   // current # of bytes in AC.chars
                  divMSb,   // most significant bit of divisor polynomial
                  pBits,    // degree (# of bits) of divisor polynomial
                  quot,     // quotient of divMSb / 8
                  max,      // maximum print position for AC.chars
                  mMessage; // # of characters in message

      private byte[] message; // copy of message in AC.chars

构造函数为所有数组分配空间,并使用文件 Cnst.cs 中定义的常量初始化数据成员。然后它调用函数 GenerateBitMasks 来初始化用于打印多项式的位掩码数组,然后调用函数 InitPolyAndMask 来初始化生成器多项式和在左移时使用的最高有效比特的掩码。

      public ShiftArray( int pOf2, uint uiP, int msb )
      {
         int i;

         poly = new _8bitShiftRegister[ Cnst.mArg ];   // divisor polynomial
         mask = new _8bitShiftRegister[ Cnst.mArg ];   // mask used by function ShiftLeftToMSb
         AC.arg = new _8bitShiftRegister[ Cnst.mArg ]; // argument to generator polynomial
         for ( i = 0; i < Cnst.mArg; ++i )
         {
            poly[ i ] = new _8bitShiftRegister();
            mask[ i ] = new _8bitShiftRegister();
            AC.arg[ i ] = new _8bitShiftRegister();
         }
         AC.chars = new _8bitShiftRegister[ Cnst.mChars + Cnst.mArg2 ]; // arg/buffer + area to
                                                                        // append zeros and remainder

         for ( i = 0; i < Cnst.nChars; ++i )
         {
            AC.chars[ i ] = new _8bitShiftRegister();
         }
         message = new byte[ Cnst.mChars + Cnst.mArg ];
         cBits = nBits = Util.Min( pOf2, Cnst.mBits );
         cBytes = nBytes = Util.BitsToBytes( nBits );
         max = cBytes + Cnst.mArg;
         divMSb = msb;
         pBits = msb + 1;
         quot = divMSb >> 3;

         Console.WriteLine( "Initializing ShiftArray\n" );
         Console.WriteLine( String.Format( "bits == {0}, bytes == {1}", nBits, nBytes ) );
         Console.WriteLine( String.Format( "generator msb == {0}, bits == {1}\nquot == {2}",
                                           divMSb, pBits, quot ) );
         GenerateBitMasks();
         InitPolyAndMask( uiP );
      }// ShiftArray

      private void GenerateBitMasks()
      {
         uint m = 0x00000001;

         uiDmask = new uint[ 32 ];

         for ( int i = 0; i < 32; ++i )
         {
            uiDmask[ i ] = m;
            m <<= 1;
         }
      }

      private void InitPolyAndMask( uint uiP )
      {
         uint uiM;

         uiPoly = uiP;
         uiMask = ( (uint)1 ) << divMSb;

         uiM = uiMask;
         for ( int i = Cnst.mArg - 1; i > -1; --i )
         {
            poly[ i ].Load( (byte)uiP );
            mask[ i ].Load( (byte)uiM );
            uiP >>= 8;
            uiM >>= 8;
         }
         Console.Write( "Generator polynomial" );
         PrintPolynomial( uiPoly );
         PrintRange( "            MSb mask == 0x", mask, Cnst.mArg );
      }// InitPolyAndMask

在描述更多函数之前,我将处理函数 SelfTest(以 _8bitShiftRegister 类的自测试功能为精神定义的),它演示了 CRC 处理的整个过程。此函数引导了代码的自上而下的开发。

      public void SelfTest()
      {
         LoadString( "M.I.T.EE" );
         GenerateCRC();
         PrintCRC();
         VerifyCRC();
      }// SelfTest

函数 LoadString 接收一个字符串形式的消息(str),清除 AC.charsAC.arg 部分,在 AC.arg 部分之后将字符串复制到 AC.chars 中,为 CRC 验证目的制作原始消息的副本,并清除 AC.chars 的其余部分。然后它计算要处理的比特数和字节数。

      public void LoadString( string str )
      {
         int i, j, m, n = str.Length, p;

         m = n > Cnst.mChars ? Cnst.mChars : n; // maximum number of characters to load
         p = m + Cnst.mArg;                     // add size of area to append zeros

         for ( i = 0; i < Cnst.mArg; ++i ) // clear AC.arg section of AC.chars
         {
            AC.chars[ i ].Clear();
         }
         for ( i = Cnst.mArg, j = 0; i < p; ++i, ++j ) // load characters from str     
         {
            byte b = (byte)str[ j ];
            AC.chars[ i ].Load( b );
            message[ j ] = b;
         }
         mMessage = j;
         while ( i < Cnst.nChars ) // clear remaining part of AC.chars
         {
            AC.chars[ i++ ].Clear();
         }
         cBits = (n << 3) + divMSb;
         cBytes = Util.BitsToBytes( cBits );
         PrintMessage( "loaded" );
      }// LoadString

正如函数 GenerateCRC 中的注释所解释的,它首先对齐被除数和除数。然后,只要还有重要的比特要处理,函数就会重复应用除数多项式到被除数,并重新对齐被除数和除数。最后,剩余的比特(构成 CRC)被全部左移。

      public void GenerateCRC()
      {
         Console.WriteLine( "\nGenerating CRC\n" );
         Console.WriteLine( "cBits cBytes | arg. |  chars\n" );
         ShiftLeftToMSb( DCR.no ); // align dividend with divisor
         while ( cBits > pBits )
         {
            ArgDivPoly();          // apply divisor polynomial
            ShiftLeftToMSb();      // re-align dividend with divisor
         }
         ShiftLeftToEnd();         // shift remainder all the way to the left
      }// GenerateCRC

就比特操作而言,左移函数和函数 ArgDivPoly 是不言自明的,并在下面定义。在整个定义中,请注意对 _8bitShiftRegister 功能的使用。

      public void ShiftLeftToMSb( DCR dcr = DCR.yes )
      {
         byte mskByte = mask[ 3 - quot ].Read();

         while ( ( cBits > 0 )
                 &&
                 ( ( AC.arg[ 3 - quot ].Read() & mskByte ) != mskByte ) )
         {
            ShiftLeft( dcr );
         }
         Print();
      }// ShiftLeftToMSb

      public void ArgDivPoly()
      {
         for ( int i = 0; i < Cnst.mArg; ++i )
         {
            AC.arg[ i ].Divide( poly[ i ] );
         }
         Print();
      }// ArgDivPoly

      public void ShiftLeftToEnd()
      {
         if ( !ZeroArg() )
         {
            Console.WriteLine( "\nShifting AC.arg to end\n" );
            while ( ( AC.arg[ 0 ].Read() & 0x80 ) != 0x80 )
            {
               ShiftLeft( DCR.no );
            }
            Print();
         }
      }// ShiftLeftToEnd

      private bool ZeroArg() // is AC.arg all 0's ?
      {
         bool zero = true;

         for ( int i = 0; i < Cnst.mArg; ++i )
         {
            if ( AC.arg[ i ].Read() != 0x00 )
            {
               zero = false; break;
            }
         }
         return zero;
      }// ZeroArg

      public void ShiftLeft( DCR dcr )
      {
         int i, m = Cnst.mChars + ( Cnst.mArg << 1 );
         byte Cout;

         if ( cBits > 0 )
         {
            Cout = 0;
            for ( i = m - 1; i > -1; --i )
            {
               Cout = AC.chars[ i ].ShiftLeft( Cout );
            }
            if ( dcr == DCR.yes )
            {
               --cBits;
               if ( ( cBits % 8 ) == 0 )
               {
                  --cBytes;
               }
            }
         }
      }// ShiftLeft

在生成 CRC 的过程中,会调用许多打印函数进行日志记录。这些函数(PrintPolynomialPrintPrintACargPrintACcharsPrintCRC)定义在文件 ShiftArray.cs 中,为了简洁起见,我将不作描述。生成样本消息的 CRC 在命令提示符窗口中产生的输出如下(为节省空间,以两列格式显示)。十六进制表示的消息中的两个末尾零 *不* 是消息的一部分。

Initializing ShiftArray

bits == 64, bytes == 8
generator msb == 5, bits == 6
quot == 0
Generator polynomial == 0x00000035 == x^5 + x^4 + x^2 + 1
            MSb mask == 0x00000020


Message: 'M.I.T.EE' loaded

         4d2e492e542e454500


Generating CRC


cBits cBytes | arg. |  chars

   69      9 000000269724972a1722a28000
   69      9 000000139724972a1722a28000
   68      9 000000272e492e542e45450000
   68      9 000000122e492e542e45450000
   67      9 000000245c925ca85c8a8a0000
   67      9 000000115c925ca85c8a8a0000
   66      9 00000022b924b950b915140000
   66      9 00000017b924b950b915140000
   65      9 0000002f724972a1722a280000
   65      9 0000001a724972a1722a280000
   64      8 00000034e492e542e4545000
   64      8 00000001e492e542e4545000
   59      8 0000003c925ca85c8a8a0000
   59      8 00000009925ca85c8a8a0000
   57      8 000000264972a1722a280000
   57      8 000000134972a1722a280000
   56      7 0000002692e542e4545000
   56      7 0000001392e542e4545000
   55      7 0000002725ca85c8a8a000
   55      7 0000001225ca85c8a8a000
   54      7 000000244b950b91514000
   54      7 000000114b950b91514000
   53      7 00000022972a1722a28000
   53      7 00000017972a1722a28000
   52      7 0000002f2e542e45450000
   52      7 0000001a2e542e45450000
   51      7 000000345ca85c8a8a0000
   51      7 000000015ca85c8a8a0000
   46      6 0000002b950b91514000
   46      6 0000001e950b91514000
   45      6 0000003d2a1722a28000
   45      6 000000082a1722a28000
   43      6 00000020a85c8a8a0000
   43      6 00000015a85c8a8a0000
   42      6 0000002b50b915140000
   42      6 0000001e50b915140000
   41      6 0000003ca1722a280000
   41      6 00000009a1722a280000
   39      5 0000002685c8a8a000
   39      5 0000001385c8a8a000
	cBits cBytes | arg. |  chars

   38      5 000000270b91514000
   38      5 000000120b91514000
   37      5 000000241722a28000
   37      5 000000111722a28000
   36      5 000000222e45450000
   36      5 000000172e45450000
   35      5 0000002e5c8a8a0000
   35      5 0000001b5c8a8a0000
   34      5 00000036b915140000
   34      5 00000003b915140000
   30      4 0000003b91514000
   30      4 0000000e91514000
   28      4 0000003a45450000
   28      4 0000000f45450000
   26      4 0000003d15140000
   26      4 0000000815140000
   24      3 00000020545000
   24      3 00000015545000
   23      3 0000002aa8a000
   23      3 0000001fa8a000
   22      3 0000003f514000
   22      3 0000000a514000
   20      3 00000029450000
   20      3 0000001c450000
   19      3 000000388a0000
   19      3 0000000d8a0000
   17      3 00000036280000
   17      3 00000003280000
   13      2 000000328000
   13      2 000000078000
   10      2 0000003c0000
   10      2 000000090000
    8      1 0000002400
    8      1 0000001100
    7      1 0000002200
    7      1 0000001700
    6      1 0000002e00

Shifting AC.arg to end

    6      1 b800000000

CRC: b8

函数 VerifyCRC 用于检查生成的 CRC 是否正确。为此,该函数重新加载原始消息,将 CRC 附加到消息,然后再次调用 GenerateCRC。结果应该是一个等于零的 CRC。

      private void VerifyCRC()
      {
         Console.WriteLine( "\nVerifying CRC\n" );
         ReloadMessage();
         AppendCRC();
         GenerateCRC();
         PrintCRC();
      }// VerifyCRC

      private void ReloadMessage()
      {
         // Copy message into AC.chars without
         // altering AC.arg

         for ( int i = Cnst.mArg, j = 0; i < max; ++i, ++j )
         {
            AC.chars[ i ].Load( message[ j ] );
         }
         PrintMessage( "reloaded" );
      }// ReloadMessage

      private void AppendCRC()
      {
         int crcBytes, crcBits, i, j;

         Console.WriteLine( "\nAppending CRC\n" );
         crcBytes = cBytes;
         crcBits = cBits;
         cBytes = nBytes;
         cBits = nBits;
         i = cBytes + Cnst.mArg;
         j = 0;
         while ( crcBytes > 0 )
         {
            AC.chars[ i++ ].Load( AC.arg[ j ].Read() );
            AC.arg[ j++ ].Clear();
            ++cBytes;
            --crcBytes;
            if ( crcBits >= 8 )
            {
               crcBits -= 8;
               cBits += 8;
            }
         }
         cBits += crcBits;
         Print();
      }// AppendCRC

CRC 验证过程中产生的输出如下。(同样,消息的十六进制表示中的两个末尾零 *不* 是消息的一部分。)

Verifying CRC


Message: 'M.I.T.EE' reloaded

         4d2e492e542e454500


Appending CRC

   70      9 000000004d2e492e542e4545b8

Generating CRC


cBits cBytes | arg. |  chars

   70      9 000000269724972a1722a2dc00
   70      9 000000139724972a1722a2dc00
   69      9 000000272e492e542e4545b800
   69      9 000000122e492e542e4545b800
   68      9 000000245c925ca85c8a8b7000
   68      9 000000115c925ca85c8a8b7000
   67      9 00000022b924b950b91516e000
   67      9 00000017b924b950b91516e000
   66      9 0000002f724972a1722a2dc000
   66      9 0000001a724972a1722a2dc000
   65      9 00000034e492e542e4545b8000
   65      9 00000001e492e542e4545b8000
   60      8 0000003c925ca85c8a8b7000
   60      8 00000009925ca85c8a8b7000
   58      8 000000264972a1722a2dc000
   58      8 000000134972a1722a2dc000
   57      8 0000002692e542e4545b8000
   57      8 0000001392e542e4545b8000
   56      7 0000002725ca85c8a8b700
   56      7 0000001225ca85c8a8b700
   55      7 000000244b950b91516e00
   55      7 000000114b950b91516e00
   54      7 00000022972a1722a2dc00
   54      7 00000017972a1722a2dc00
   53      7 0000002f2e542e4545b800
   53      7 0000001a2e542e4545b800
   52      7 000000345ca85c8a8b7000
   52      7 000000015ca85c8a8b7000
   47      6 0000002b950b91516e00
   47      6 0000001e950b91516e00
   46      6 0000003d2a1722a2dc00
   46      6 000000082a1722a2dc00
   44      6 00000020a85c8a8b7000
   44      6 00000015a85c8a8b7000
   43      6 0000002b50b91516e000
   43      6 0000001e50b91516e000
   42      6 0000003ca1722a2dc000
   42      6 00000009a1722a2dc000
	cBits cBytes | arg. |  chars

   40      5 0000002685c8a8b700
   40      5 0000001385c8a8b700
   39      5 000000270b91516e00
   39      5 000000120b91516e00
   38      5 000000241722a2dc00
   38      5 000000111722a2dc00
   37      5 000000222e4545b800
   37      5 000000172e4545b800
   36      5 0000002e5c8a8b7000
   36      5 0000001b5c8a8b7000
   35      5 00000036b91516e000
   35      5 00000003b91516e000
   31      4 0000003b91516e00
   31      4 0000000e91516e00
   29      4 0000003a4545b800
   29      4 0000000f4545b800
   27      4 0000003d1516e000
   27      4 000000081516e000
   25      4 00000020545b8000
   25      4 00000015545b8000
   24      3 0000002aa8b700
   24      3 0000001fa8b700
   23      3 0000003f516e00
   23      3 0000000a516e00
   21      3 0000002945b800
   21      3 0000001c45b800
   20      3 000000388b7000
   20      3 0000000d8b7000
   18      3 000000362dc000
   18      3 000000032dc000
   14      2 00000032dc00
   14      2 00000007dc00
   11      2 0000003ee000
   11      2 0000000be000
    9      2 0000002f8000
    9      2 0000001a8000
    8      1 0000003500
    8      1 0000000000
    0      0 00000000

CRC: 00

正如预期的那样,结果 CRC 为零。最后要做的就是编写一个主程序来启动移位数组自测试的 CRC 计算。

namespace PolynomialDivision
{
   class Program
   {
      static void Main( string[] args )
      {
         ShiftArray shArray = new ShiftArray( 64, 0x00000035, 5 );

         shArray.SelfTest();
      }
   }// Program (class)
}// PolynomialDivision (namespace)

此程序创建一个移位数组,将一个 2 的幂(64 位)传递给它,以帮助计算需要处理的最小比特数,生成器多项式的十六进制表示,以及除数最高有效比特的位置。

运行代码

要执行代码,请将其解压缩到 Visual Studio 2010(或更高版本)的 Projects 目录下的合适子目录中,并将其构建为控制台应用程序。本文关于多项式除法的数据结构到此结束。

© . All rights reserved.