• CRC循环冗余校验码的生成


      众所周知,不可能有永远都不会出错的人,同样也不可能有永远不出错的计算机,永远不出错的数据。

      人有知错能改的觉悟,计算机也有,不过计算机没有人类聪明,只能通过一个特定的方法进行自我改正,这就是校验码存在的必要了。

      一般用得比较多的校验码有奇偶校验码,CRC循环冗余校验码,海明校验码等。

      这里只介绍用的最多的CRC循环冗余校验码。

    何为校验码

      校验码是通过一种计算方法,发出端在原始数据的尾部添加若干数据;然后接收端通过计算得出数据有无错误,并且能把错误的数据还原成正确的数据。

    例如,原始数据为1010,我们通过在其尾部添加若干数据,比如101,则数据变成了1010101,将这个新的数据发送出去;假如在发送过程中出现错误,数据变成了1110101,则接收端能根据1110101还原成原始正确的数据1010。

    CRC循环冗余校验码的特点

      CRC码是基于模2运算建立编码规律的校验码(模2运算可以简单的理解为异或运算)。

    编码方法:

       1.将原始数据用一个多项式M(x) = A*X^(n-1)+B*X^(n-2)+…+N*X^(1)+P*X^(0)

                       比如说原始数据是1010,则表示为M(x) = 1*X^(3)+0*X^(2)+1*X^(1)+0*X^(0)

       2.将原始数据左移k位,得到M(X)*X^k,形成了n+k位信息。

                       上面的1010,左移3位,得到1010 000

       3.用多项式M(X)*X^k 除以(或者说异或)一个特定的生成多项式G(X),所得余数则为需要拼接到原始数据尾部的CRC校验码。

    例:

      已知原始数据为1100,生成多项式G(X)=1011,则求算CRC码的过程如下:

    已知  :M(X) = 1100 = X^3+X^2      (n=4)

    由        G(X) = 1011 = X^3+X^1+1

    得        k+1 = n = 4

    得       k=3,需要左移3位

    故得     M(X)*X^3 = 1100 000

    (M(X)*X^3) / G(X)计算过程:

      1100 000                M(X)*X^3

      1011                      G(X)

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

        111 0                     余数,高位0省去;然后余数左移1位,继续异或G(X)

        101 1                  G(X)

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

          10 10                  左移1位,继续异或G(X)

          10 11

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

                10                  由于k=3,而算到这里我们已经对余数移位2次,还需1次

    则最后算出来的结果是010,则原始数据变成了1100 010,最后三位是CRC码。

    很容易发现,如果我们需要算k位码,则G(X)的位数应该是K+1.

    由于最后的数据变成了7位,而有效数据还是4位,故上述的1100 010码又叫做(7,4)码,对应的还有(7,3)码,(7,6)码等。

    CRC码的译码规则和纠错方法

       还是刚刚的例子,我们用最后得出的数据除以G(X),发现其余数为0,这个就是正确的数据。

    我们尝试改动其中一位,比如说第2位,得到:1000 010,我们用这个数除以G(X),发现余数为111,余数不为0,则说明数据有错了!

    下面给出每个位出错对应的余数:

    序号

    N1  N2  N3  N4  N5  N6  N7

      余数

      出错位

    正确

    1    1    0    0    0    1    0

    000

    错误

    1    1    0    0    0    1    1

    001

    7

    错误

    1    1    0    0    0    0    0

    010

    6

    错误

    1    1    0    0    1    1    0

    100

    5

    错误

    1    1    0    1    0    1    0

    011

    4

    错误

    1    1    1    0    0    1    0

    110

    3

    错误

    1    0    0    0    0    1    0

    111

    2

    错误

    0    1    0    0    0    1    0

    101

    1

    假如我们现在收到一个错误的信息是1000 010,通过计算可以得出余数是111,但是如何得知是第几位出错了?

    我们试着对余数111补0后继续除下去,发现下一次的余数变成了101,继续下去,又变成了001,以后依次出现为010,100,011。。。反复循环,这就是“循环码”名字的由来。

    这样,假如N2出现错误,则出现了不为0的余数后,一方面对余数补0继续模2除,另一方面将被检测的校验码字循环左移,当余数为101(即数据的首位为0)时,通过异或门可以将其纠正后在下一次移位时送回N2。这样当移满一个循环后,就得到一个纠正后的数据了。

    最后给出几个标准

       在上面的计算中可以发现,G(X)的作用非常重要,生成CRC码和译码都需要它。值得指出的是,并不是随便一个(K+1)位数据都可以作为生成多项式,它要满足的条件:

            1.任何一位出错,都不能使余数为0,这是非常显然的;

            2.不同位发生错误后,余数不能相同;

            3.对余数继续模2除,应使余数循环。

    为了方便起见,也为了标准化工作,现在一般用的生成多项式有以下几个:

    CRC-16 = X16 + X15 + X2 + X0           美国二进制同步系统中采用

    CRC-CCITT = X16 + X12 + X5 + X0        由欧洲CCITT 推荐

    CRC-32 = X32 + X26 + X23 + X22 + X16 + X11 + X10 + X8 + X7 + X5 + X4 + X2 + X1 + X0

    下面贴出生成CRC码的代码

    #include <iostream>
    #include <cstdio>
    
    using namespace std;
    
    #define  CRC_16   0x18005
    // CRC-16 = X16 + X15 + X2 + X0
    #define  CRC_CCITT  0x11021
    // CRC-CCITT = X16 + X12 + X5 + X0
    #define  CRC_32  0x104C11DB7
    // CRC-32 = X32 + X26 + X23 + X22 + X16 + X11 + X10 + X8 + X7 + X5 + X4 + X2 + X1 + X0
    
    #define  CHECK_CODE  0x8000
    // check_code = x15,判断被除数位数>=16
    //check_code是为了每次做异或运算时保证位数是足够的
    
    unsigned long table[256];
    
    
    void build_16(unsigned long poly)
    {
        unsigned long x;
        unsigned long t;
    
        /*
        这里是将原始数据直接写入到t中
        for (unsigned long i=0; i<256; i++)
        {
            x = i << 8;
                  t = 0;
                    for (unsigned long j = 0; j < 8; j++)
                    {
                            if ( (x ^ t) & CHECK_CODE)
                                t = (t << 1) ^ poly;
                            else
                                t <<= 1;
                            x <<= 1;
                    }
                    table[i] = ( unsigned long ) t;
            }
        
        */
        //下面的是把余数和原始数据分开的写法
        //原始数据只有8位
        for (int i=0; i<256; i++)
        {
            x = i<<8;
            //只移8位是为了位数对齐,因为下面的运算都是低位对齐的
            for (int j=0; j<8; j++)
            {
                if (x&CHECK_CODE)
                {
                    x <<= 1;
                    x = x^poly;
                }
                else x <<= 1;
            }
            table[i] = (i<<16)+x;
        }
    }
    
    void build_32(unsigned long poly)
    {
        unsigned long x;
        
        for (unsigned long i=0; i<256; i++)
        {
            x = i<<24;
            for (unsigned long j=0; j<8; j++)
            {
                if (x & 0x80000000)    //判断位数是否足够,32位
                {
                    x <<= 1;
                    x = x^poly;
                }
                else x <<= 1;
            }
            table[i] = (i<<32)+x;
        }
    }
    
    int main()
    {
        build_16(CRC_16);
        build_16(CRC_CCITT);
    
        build_32(CRC_32);
        
        for (int i=0; i<256; i++)
        {
            if (i % 10 == 0)
                cout<<endl;
            printf("%lx ", table[i]);
        }
        
        return 0;
    }
  • 相关阅读:
    20220507 Using Spring Boot
    20220606 Java工具类,去重10亿手机号码
    20220605 JVM下篇:性能监控与调优篇 5. 分析 GC 日志
    20220506 Documentation Overview
    20220506 Upgrading Spring Boot Applications
    20220605 JVM下篇:性能监控与调优篇 4. JVM 运行时参数
    eclipse无法加载Layout(Eclipse is loading framework information and the layout library from the SDK fold)
    安卓app_sl3.8相对布局管理器
    安卓app_sl3.13xml按钮 onClick属性指定对应的方法与java事件监听器的方法
    安卓app_sl3.12登记注册信息获取编辑框的值打印到Log日志显示
  • 原文地址:https://www.cnblogs.com/ay27/p/2988650.html
Copyright © 2020-2023  润新知