CRC校验算法的数学原理(上)

介绍

  CRC是Cyclic Redundancy Check的缩写,用中文来讲,就是 循环冗余校验。是一种通过对数据产生固定位数校验码以备侦测数据错误的数据校验技术,主要用来侦测数据传输错误,也可用来校验存储的数据的完整性。

内容

一、CRC校验算法数学原理

  CRC校验的数学基础是模2剩余类域上的一元多项式环,所以下面先大致的不那么严谨的介绍下模2剩余类域上的一元多项式环以及相关的性质。如果想详细了解,建议找本《高等代数》的书看下。

1. 模2剩余类域

  由0和1构成的集合,定义了以下的加法与乘法运算,并且加法满足交换律、结合律,乘法满足交换律、结合律,加法与乘法满足左、右分配律,便是一个 模2剩余类域

  • 加法规则:
    • 1 + 1 = ( 1 + 1 ) m o d    2 = 0 1+1 = (1+1) \mod 2 = 0 1+1=(1+1)mod2=0
    • 1 + 0 = ( 1 + 0 ) m o d    2 = 1 1+0 = (1+0) \mod 2 = 1 1+0=(1+0)mod2=1
    • 0 + 1 = ( 0 + 1 ) m o d    2 = 1 0+1 = (0+1) \mod 2 = 1 0+1=(0+1)mod2=1
    • 0 + 0 = ( 0 + 0 ) m o d    2 = 0 0+0 = (0+0) \mod 2 = 0 0+0=(0+0)mod2=0
  • 乘法规则:
    • 1 ⋅ 1 = ( 1 ⋅ 1 ) m o d    2 = 1 1 \cdot 1 = (1 \cdot 1) \mod 2 = 1 11=(11)mod2=1
    • 1 ⋅ 0 = ( 1 ⋅ 0 ) m o d    2 = 0 1 \cdot 0 = (1 \cdot 0) \mod 2 = 0 10=(10)mod2=0
    • 0 ⋅ 1 = ( 0 ⋅ 1 ) m o d    2 = 0 0 \cdot 1 = (0 \cdot 1) \mod 2 = 0 01=(01)mod2=0
    • 0 ⋅ 0 = ( 0 ⋅ 0 ) m o d    2 = 0 0 \cdot 0 = (0 \cdot 0) \mod 2 = 0 00=(00)mod2=0

  模2剩余类域上,减法是基于加法定义的,减一个数就是加上这个数的负元。某个数的负元就是跟这个数相加等于0的的那个数。于是,由上面的加法规则,可以知道,1的负元是1自己,0的负元是0自己。于是便有如下的减法运算。

  • 减法规则:
    • 1 − 1 = 1 + 1 = ( 1 + 1 ) m o d    2 = 0 1-1 = 1+1 = (1+1) \mod 2 = 0 11=1+1=(1+1)mod2=0
    • 1 − 0 = 1 + 0 = ( 1 + 0 ) m o d    2 = 1 1-0 = 1+0 = (1+0) \mod 2 = 1 10=1+0=(1+0)mod2=1
    • 0 − 1 = 0 + 1 = ( 0 + 1 ) m o d    2 = 1 0-1 = 0+1 = (0+1) \mod 2 = 1 01=0+1=(0+1)mod2=1
    • 0 − 0 = 0 + 0 = ( 0 + 0 ) m o d    2 = 0 0-0 = 0+0 = (0+0) \mod 2 = 0 00=0+0=(0+0)mod2=0

  由上面,可以看出,模2剩余类域上的减法与加法运算法则是一样的,没有区别。另外,若把模2剩余类域中的数看作二进制数位,便可发现,其加法运算法则与减法运算法则,与二进制数位的异或运算法则等同

2. 模2剩余类域上的一元多项式环

  如下所示的以模2剩余类域中的数为系数的一元多项式组合的集合,并定义了如下的加法与乘法运算,便是 模2剩余类域上的一元多项式环,一般可记作 f ( x ) f(x) f(x),其最高次项中 x x x的指数 n n n称为该多项式的 次数,一般将 f ( x ) f(x) f(x)的次数记作 d e g f ( x ) deg f(x) degf(x)
a n x n + a n − 1 x n − 1 + ⋯ + a 1 x + a 0    ( a n , a n − 1 , ⋯   , a 1 , a 0 ∈ { 0 , 1 } , a n ≠ 0 ) a_nx^n + a_{n-1}x^{n-1} + \cdots + a_1x + a_0 \space \space (a_n, a_{n-1}, \cdots, a_1, a_0 \in \{0, 1\},a_n \not = 0) anxn+an1xn1++a1x+a0  (an,an1,,a1,a0{0,1}an=0)

  • 加法规则:两个多项式相加,就是普通的多项式相加规则,只是系数按模2剩余类域上运算规则运算。
  • 乘法规则:两个多项式相乘,就是普通的多项式相乘规则,只是系数按模2剩余类域上运算规则运算。

  模2剩余类域上的一元多项式环上的减法也是由加法定义的,减去一个模2剩余类域上的一元多项式就是加上它的负元。而某个模2剩余类域上的一元多项式的负元,就是与之相加等于0的那个模2剩余类域上的一元多项式。受模2剩余类域上运算法则影响,明显某个模2剩余类域上的一元多项式的负元就是它自己。从而在模2剩余类域上的一元多项式环上,减法运算法则也是与加法运算法则一样的。另外,若把模2剩余类域上的一元多项式环中的系数看作二进制数位串,便可发现,其加法运算法则与减法运算法则,与二进制数位串的异或运算法则等同

  域上的一元多项式环有一个重要的性质,就是,对于任意的多项式 f ( x ) f(x) f(x)与不为0的多项式 p ( x ) p(x) p(x),存在唯一的一对多项式 q ( x ) q(x) q(x) r ( x ) r(x) r(x),使得下式成立,其中 r ( x ) r(x) r(x)的次数小于 p ( x ) p(x) p(x)的次数。模2剩余类域上的一元多项式环作为一种类型的域上的一元多项式环,同样也有这个性质。其中 q ( x ) q(x) q(x)称为 商式 r ( x ) r(x) r(x)称为 余式,(1)式称为 f ( x ) f(x) f(x) p ( x ) p(x) p(x)除法算式
f ( x ) = p ( x ) ⋅ q ( x ) + r ( x )    ( deg ⁡   r ( x ) < deg ⁡   p ( x ) )    ( 1 ) f(x) = p(x) \cdot q(x) + r(x) \space \space (\deg \space r(x) < \deg \space p(x)) \space \space \color{red}{(1)} f(x)=p(x)q(x)+r(x)  (deg r(x)<deg p(x))  (1)

  对于任意一个二进制串,我们按从右到左的顺序,依次给每一位二进制数所在位进行编号,编号从0开始,依次递增1。 于是,以数位上的数为系数,以数位编号作为次数,便可以将每一个二进制串与一个模2剩余类域上的一元多项式对应起来。比如如下右边的模2剩余类域上的一元多项式便是与左边二进制串对应的。注意,由于先导0的存在,对应并不是一一对应的,因为先导的系数为0项不影响一个多项式的值,因此一个多项式可能对应多个二进制串,这些串的区别只是先导0个数不一样。但是对于每一个二进制串,只有唯一的多项式与之对应。一个多项式,最高次项以及比最高次项次数低的项(不论系数是否为0),都是有效项。不过一般表示时,略去系数为0的有效项。
0 → 0 00 → 0 1 → 1 01 → 1 1010   1001 → x 7 + x 5 + x 3 + 1 0   1010   1001 → x 7 + x 5 + x 3 + 1 1001   1010   1001 → x 11 + x 8 + x 7 + x 5 + x 3 + 1 00   1001   1010   1001 → x 11 + x 8 + x 7 + x 5 + x 3 + 1 1101   0101   1010   1001 → x 15 + x 14 + x 12 + x 10 + x 8 + x 7 + x 5 + x 3 + 1 0000   1101   0101   1010   1001 → x 15 + x 14 + x 12 + x 10 + x 8 + x 7 + x 5 + x 3 + 1 0 \rightarrow 0\\ 00 \rightarrow 0\\ 1 \rightarrow 1\\ 01 \rightarrow 1\\ 1010 \space 1001 \rightarrow x^7 + x^5 + x^3 + 1\\ 0 \space 1010 \space 1001 \rightarrow x^7 + x^5 + x^3 + 1\\ 1001 \space 1010 \space 1001 \rightarrow x^{11} + x^8 + x^7 + x^5 + x^3 + 1\\ 00 \space 1001 \space 1010 \space 1001 \rightarrow x^{11} + x^8 + x^7 + x^5 + x^3 + 1\\ 1101 \space 0101 \space 1010 \space 1001 \rightarrow x^{15} + x^{14} + x^{12} + x^{10} + x^8 + x^7 + x^5 + x^3 + 1\\ 0000 \space 1101 \space 0101 \space 1010 \space 1001 \rightarrow x^{15} + x^{14} + x^{12} + x^{10} + x^8 + x^7 + x^5 + x^3 + 1\\ 00000110111010 1001x7+x5+x3+10 1010 1001x7+x5+x3+11001 1010 1001x11+x8+x7+x5+x3+100 1001 1010 1001x11+x8+x7+x5+x3+11101 0101 1010 1001x15+x14+x12+x10+x8+x7+x5+x3+10000 1101 0101 1010 1001x15+x14+x12+x10+x8+x7+x5+x3+1

3. CRC校验算法发送端部分

  现在,我们用上面的知识点来构造CRC校验发送端。

  1. 对于任意一个二进制串 D D D,我们可以把它看作一个模2剩余类域上的一元多项式 d ( x ) d(x) d(x),我们在它最右边加上 m m m个0位,我们可以把加了0位后的串看作一个模2剩余类域上的一元多项式 f ( x ) f(x) f(x)。可以看出有如下等式。
    f ( x ) = d ( x ) ⋅ x m    ( 2 ) f(x) = d(x) \cdot x^m \space \space \color{red}{(2)} f(x)=d(x)xm  (2)
  2. 然后我们取另一个位数固定为 m + 1 m+1 m+1的二进制串 P P P,其最左边的位上的数为1,并把它看作一个模2剩余类域上的一元多项式 p ( x ) p(x) p(x),明显 p ( x ) p(x) p(x)不为0,并且其次数为 m m m
  3. 根据上面说的域上的一元多项式环的性质,于是我们能找到唯一的一对模2剩余类域上的一元多项式 q ( x ) q(x) q(x) r ( x ) r(x) r(x),使之满足下面除法算式,其中 r ( x ) r(x) r(x)次数最大为 m − 1 m-1 m1。我们弃掉 q ( x ) q(x) q(x),只要 r ( x ) r(x) r(x),并把它转换为最短二进制串,也就是没有先导0的二进制串,这个是唯一的。
    f ( x ) = p ( x ) ⋅ q ( x ) + r ( x )    ( deg ⁡   r ( x ) < deg ⁡   p ( x ) )    ( 3 ) f(x) = p(x) \cdot q(x) + r(x) \space \space (\deg \space r(x) < \deg \space p(x)) \space \space \color{red}{(3)} f(x)=p(x)q(x)+r(x)  (deg r(x)<deg p(x))  (3)
  4. 由于 r ( x ) r(x) r(x)的次数最大为 m − 1 m-1 m1,所以 r ( x ) r(x) r(x)转换成的最短二进制串,其位数小于等于 m m m。对于位数不足 m m m的,补充先导0位至位数为 m m m位,称这个数据为 R R R
  5. 然后我们把这个 m m m位的串 R R R添加到串 D D D的右边,便得到了串 D 1 D_1 D1。设 D 1 D_1 D1的模2剩余类域上的一元多项式为 d 1 ( x ) d_1(x) d1(x),则有如下等式。
    d 1 ( x ) = d ( x ) ⋅ x m + r ( x )    ( 4 ) d_1(x) = d(x) \cdot x^m + r(x) \space \space \color{red}{(4)} d1(x)=d(x)xm+r(x)  (4)

  至此,我们便完成了CRC校验发送端的操作。这个串 D 1 D_1 D1,我们称为带了CRC校验信息的串,是可以发出去了的串。

4. CRC校验算法接收端部分

  现在,我们来看看CRC校验接收端。假设接收到的串为 D 2 D_2 D2,设其模2剩余类域上的一元多项式为 d 2 ( x ) d_2(x) d2(x)

  结合(2)、(4)式,我们有
d 1 ( x ) = f ( x ) + r ( x ) d_1(x) = f(x) + r(x) d1(x)=f(x)+r(x)
由于模2剩余类域上的一元多项式环上加法与减法等同,所以有
d 1 ( x ) = f ( x ) − r ( x )    ( 5 ) d_1(x) = f(x) - r(x) \space \space \color{red}{(5)} d1(x)=f(x)r(x)  (5)
又由(3)可得
f ( x ) − r ( x ) = p ( x ) ⋅ q ( x )    ( 6 ) f(x) - r(x) = p(x) \cdot q(x) \space \space \color{red}{(6)} f(x)r(x)=p(x)q(x)  (6)
结合(5)、(6),也就是有
d 1 ( x ) = p ( x ) ⋅ q ( x )    ( 7 ) d_1(x) = p(x) \cdot q(x) \space \space \color{red}{(7)} d1(x)=p(x)q(x)  (7)
也就是,如果接收端接到的信息没出错,即 D 2 = D 1 D_2 = D_1 D2=D1,那么便有 d 2 ( x ) = d 1 ( x ) d_2(x) = d_1(x) d2(x)=d1(x),便有 d 2 ( x ) = p ( x ) ⋅ q ( x ) d_2(x) = p(x) \cdot q(x) d2(x)=p(x)q(x)。于是 d 2 ( x ) d_2(x) d2(x)对于 p ( x ) p(x) p(x)的除法算式中,余式为0。反之,如果 d 2 ( x ) d_2(x) d2(x)对于 p ( x ) p(x) p(x)的除法算式中,余式不为0,便可断定 D 2 ≠ D 1 D_2 \not = D_1 D2=D1,接收数据出错。

  根据上面的分析,可以如下构造CRC校验的接收端。

  1. 对于收到的数据 D 2 D_2 D2,其模2剩余类域上的一元多项式为 d 2 ( x ) d_2(x) d2(x),根据 d 2 ( x ) d_2(x) d2(x) p ( x ) p(x) p(x)的除法算式,求得余式。注意,这儿的 p ( x ) p(x) p(x)就是发送端选取的那个。
  2. 如果余式不为0,便可断定数据出错。

  另外,也可以用与发送端一样的方法,也就是把收到的 D 2 D_2 D2拆成两部分,一部分是与 D D D对应的部分,设为 D ′ D' D,另一部分便是与 R R R对应的部分,记为 R ′ R' R。然后用发送端算法对 D ′ D' D重新进行除法算式取得余式,看与 R ′ R' R表示的式子是否相等。若是不一样,则可以断定 D 2 D_2 D2 D 1 D_1 D1不等,接收数据出错。

  由于发送端与接收端其是可以共用发送端算法,所以CRC校验算法一般主要就是指发送端这部分。

二、面向位的CRC校验算法

1. 通用求余算法

  从上面构造的CRC校验算法的发送端和接收端可以看出,关键的步骤是求一个式子对另一个式子的除法算式,而在这个除法算式中,我们只需要余式,而不需要关心商式。所以,更具体一点,就是要求一个式子对另一个式子的余式。

  对于域上的一元多项式环,有一个通用的求余式算法。求 f ( x ) f(x) f(x) p ( x ) p(x) p(x)的余式, p ( x ) p(x) p(x)不为0,通用求余算法描述如下:

  1. 如果 f ( x ) f(x) f(x)的次数小于 p ( x ) p(x) p(x)的次数,则 f ( x ) f(x) f(x)便是余式。从下式可以看出。
    f ( x ) = p ( x ) ⋅ 0 + f ( x )    ( deg ⁡ f ( x ) < deg ⁡ p ( x ) ) f(x) = p(x) \cdot 0 + f(x) \space \space (\deg f(x) < \deg p(x)) f(x)=p(x)0+f(x)  (degf(x)<degp(x))
  2. 如果 f ( x ) f(x) f(x)的次数大于或者等于 p ( x ) p(x) p(x)的次数,设 f ( x ) f(x) f(x)的最高次项为 a n x n a_nx^n anxn a n a_n an不为0,设 p ( x ) p(x) p(x)的最高次项为 b m x m b_mx^m bmxm b m b_m bm不为0,则取 a n b m − 1 x n − m a_nb_m^{-1}x^{n-m} anbm1xnm,记为 q 1 ( x ) q_1(x) q1(x),其中 b m − 1 b_m^{-1} bm1 b m b_m bm的逆元,域中一个数的 逆元 就是与这个数乘积为1的那个数,域中每一个非零数都有唯一的逆元。于是有下面的(8)式,记为 r 1 ( x ) r_1(x) r1(x),可知 r 1 ( x ) r_1(x) r1(x)的次数小于 f ( x ) f(x) f(x)的次数,于是有下面(9)式,暂称(9)式为 f ( x ) f(x) f(x) p ( x ) p(x) p(x)降次除法算式,暂称 q 1 ( x ) q_1(x) q1(x) f ( x ) f(x) f(x) p ( x ) p(x) p(x)伪商式,暂称 r 1 ( x ) r_1(x) r1(x) f ( x ) f(x) f(x) p ( x ) p(x) p(x)伪余式
    f ( x ) − p ( x ) ⋅ a n b m − 1 x n − m    ( 8 ) f(x) - p(x) \cdot a_nb_m^{-1}x^{n-m} \space \space \color{red}{(8)} f(x)p(x)anbm1xnm  (8)
    f ( x ) = p ( x ) ⋅ q 1 ( x ) + r 1 ( x )    ( deg ⁡ r 1 ( x ) < deg ⁡ f ( x ) )    ( 9 ) f(x) = p(x) \cdot q_1(x) + r_1(x) \space \space (\deg r_1(x) < \deg f(x)) \space \space \color{red}{(9)} f(x)=p(x)q1(x)+r1(x)  (degr1(x)<degf(x))  (9)
  3. 对求得的伪余式,按第2步再求这个伪余式对 p ( x ) p(x) p(x)的伪余式,如此重复,直到所得的伪余式的次数小于 p ( x ) p(x) p(x)的次数,这时的伪商式记为 q l ( x ) q_l(x) ql(x),这时的伪余式记为 r l ( x ) r_l(x) rl(x),由下式可知 r l ( x ) r_l(x) rl(x)便是 f ( x ) f(x) f(x) p ( x ) p(x) p(x)的余式。
    f ( x ) = p ( x ) ⋅ q 1 ( x ) + r 1 ( x ) = p ( x ) ⋅ q 1 ( x ) + p ( x ) ⋅ q 2 ( x ) + r 2 ( x ) ⋮ = p ( x ) ⋅ q 1 ( x ) + p ( x ) ⋅ q 2 ( x ) + ⋯ + p ( x ) ⋅ q l ( x ) + r l ( x ) = p ( x ) ⋅ ( q 1 ( x ) + q 2 ( x ) + ⋯ + q l ( x ) ) + r l ( x )    ( deg ⁡ r l ( x ) < deg ⁡ p ( x ) ) \begin{aligned} f(x) &= p(x) \cdot q_1(x) + r_1(x)\\ &= p(x) \cdot q_1(x) + p(x) \cdot q_2(x) + r_2(x)\\ &\vdots\\ &= p(x) \cdot q_1(x) + p(x) \cdot q_2(x) + \cdots + p(x) \cdot q_l(x) + r_l(x)\\ &= p(x) \cdot (q_1(x) + q_2(x) + \cdots + q_l(x)) + r_l(x) \space \space (\deg r_l(x) < \deg p(x))\\ \end{aligned} f(x)=p(x)q1(x)+r1(x)=p(x)q1(x)+p(x)q2(x)+r2(x)=p(x)q1(x)+p(x)q2(x)++p(x)ql(x)+rl(x)=p(x)(q1(x)+q2(x)++ql(x))+rl(x)  (degrl(x)<degp(x))

2. 通用求余算法例子

  结合上面的讲的域上的一元多项式环通用求余算法,我们在模2剩余类域上的一元多项式环中,求下面的(10)式对(11)式的余式。
x 31 + x 29 + x 28 + x 24 + x 22 + x 20 + x 16 + x 12 + x 4 + x 3    ( 10 ) x^{31} + x^{29} + x^{28} + x^{24} + x^{22} + x^{20} + x^{16} + x^{12} + x^{4} + x^{3} \space \space \color{red}{(10)} x31+x29+x28+x24+x22+x20+x16+x12+x4+x3  (10)
x 16 + x 15 + x 12 + x 8 + 1    ( 11 ) x^{16} + x^{15} + x^{12} + x^{8} + 1 \space \space \color{red}{(11)} x16+x15+x12+x8+1  (11)

  求(10)式对(11)式的余式的详细过程如下图:

在这里插入图片描述
图示 ( 1 ) \color{red}{图示(1)} 图示(1)

3. 面向位的CRC求余算法的实现(C语言)

  结合通用求余算法与上面的模2剩余类域上的一元多项式环中求余例子,可以发现模2剩余类域上的一元多项式环中求余如下几个特点。

  • 由于多项式最高次项系数不能为0,所以最高次项系数便只有1,所以不同多项式只要最高次项次数一样,最高次项便可以相抵。
  • 由于每一轮迭代中, r n − 1 ( x ) r_{n-1}(x) rn1(x) p ( x ) ⋅ q n ( x ) p(x) \cdot q_n(x) p(x)qn(x)的次数是一样的,也就是最高次项必然相抵,而且 p ( x ) ⋅ q n ( x ) p(x) \cdot q_n(x) p(x)qn(x)只有前 m + 1 m+1 m+1有效项(包括系数为0的项)不一定为0,而后面的有效项全是0,所以实际进行相减运算的只有前 m + 1 m+1 m+1有效项中去掉最高次项的 m m m项,后面的有效项只是从 r n − 1 ( x ) r_{n-1}(x) rn1(x)照搬到 r n ( x ) r_n(x) rn(x)
  • 由于只需要伪余式的信息,所以每一轮迭代只需要关注前 m + 1 m+1 m+1有效项中去掉最高次项的 m m m项。
  • 对于每一轮迭代中项的运算,实际上只是项的系数在参与运算,项的次数的运算由对齐隐式实现了。

  由于每次迭代实际上相当于,只进行 m m m项的减法运算,也只需要暂存结果中的 m m m项,又由于模2剩余类域上的一元多项式环中减法运算与异或运算等同,所以相当于每次迭代只进行 m m m项的异或运算并暂存其 m m m项结果。因此需要一个能存储 m m m项的暂存器。

  从上面的例子中,可以看出,暂存器在 f ( x ) f(x) f(x)上从左往右走,不停的通过减法运算把左边项抵消,然后从右边补项。整个过程实际上相当于:

  1. 如果 f ( x ) f(x) f(x)次数小于 m + 1 m+1 m+1 f ( x ) f(x) f(x)便是要求的余式。如果 f ( x ) f(x) f(x)大于等于 m + 1 m+1 m+1位,取 f ( x ) f(x) f(x)的前 m m m个有效项到到暂存器,将待移入标记指向 f ( x ) f(x) f(x)剩下的有效项的最左边的项。
  2. 暂存器在 f ( x ) f(x) f(x)上向右走一项,也就是把暂存器内容整体左移1项,然后用待移入标记指向的项,补充暂存器最右边的空出的项,调整待移位标记指向 f ( x ) f(x) f(x)中下一个有效项。记住移出的项的系数。
  3. 如果上一步移出的项的系数是1,就是将暂存器中的 m m m项的系数与 p ( x ) p(x) p(x)的后 m m m有效项系数对应进行异或,所得结果作为暂存器中的 m m m项对应的系数。
  4. 重复上面的第2~3步,直到待移入标记不再指向 f ( x ) f(x) f(x)中的有效项,也就是 f ( x ) f(x) f(x)中所有的有效项都移入过暂存器。这时暂存器中的内容就是 f ( x ) f(x) f(x) p ( x ) p(x) p(x)的余式。

  具体到实现,需要区别 p ( x ) p(x) p(x)的次数, p ( x ) p(x) p(x)的次数为8、16和32时,就对应泛称的8位、16位和32位的CRC算法。根据上述算法,可以知道 p ( x ) p(x) p(x)的最高次项不参与运算,所以只需要 p ( x ) p(x) p(x)去掉最高次项的剩余有效项,从而8、16和32位CRC算法使用的也是裁了最高次项的只有8项的、16项的和32项的有效项。

  另外,数据一般是以字节为单位提供的,字节串转换为位串时,需要区分位序,如果每个字节以最高有效位(MSB)到最低有效位(LSB)的顺序提取位,也就是从左到右提取位,那就称MSB优先的。如果每个字节以最低有效位(LSB)到最高有效位(MSB)的顺序提取位,也就是从右到左提取位,那就称LSB优先的。

  由于在 f ( x ) f(x) f(x)前面添加先导系数为0的项,不改变CRC求余结果,所以暂存器中初始值给0是不影响CRC求余结果的,这样做也简化了上述算法第1步实现。

  下面给出适用于8位、16位和32位的MSB优先的面向位的CRC求余算法的C语言代码,并使用上面的例子来测试。C语言代码及运行结果如下:

#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>


uint32_t crc_mod_bit_oriented_msb_first(const uint8_t *data,
                                        int length,
                                        uint32_t polynomial,
                                        int width) {
  uint32_t register1 = 0;
  uint32_t significant_mask = 0xffffffff >> (32 - width);
  uint32_t register_msb_mask = 1 << (width - 1);
  uint32_t register_lsb_mask = 1;
  uint32_t byte_msb_mask = 0x80;

  for (int i = 0; i < length; ++i) {
    uint32_t byte = data[i];

    for (int j = 0; j < 8; ++j) {
      bool need_xor = ((register1 & register_msb_mask) == register_msb_mask);
      register1 <<= 1;

      bool need_or = ((byte & byte_msb_mask) == byte_msb_mask);
      byte <<= 1;

      if (need_or) register1 |= register_lsb_mask;
      if (need_xor) register1 ^= polynomial;
    }
  }

  return register1 & significant_mask;
}

int main(int argc, const char *argv[]) {
  uint8_t f[] = "\xb1\x51\x10\x18";
  uint32_t p = 0x9101;

  uint32_t crc_mod = crc_mod_bit_oriented_msb_first(f, sizeof(f) - 1, p, 16);
  printf("\"%02X%02X%02X%02X\" mod \"%04X\" in crc way: \"%04X\"\n", f[0], f[1], f[2], f[3],
         p, crc_mod);

  return 0;
}
"B1511018" mod "9101" in crc way: "CA45"

4. 面向位的CRC校验算法实现(C语言)

  需要注意,CRC校验发送端算法, f ( x ) f(x) f(x)是由上面的(2)式得到的,也就是对于待处理数据 D D D,要先在最后面附上 m m m位的0,再进行CRC求余。

  下面给出适用于8位、16位和32位的MSB优先的面向位的CRC校验算法的C语言代码及运行结果如下:

#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>


uint32_t crc_bit_oriented_msb_first(const uint8_t *data,
                                    int length,
                                    uint32_t polynomial,
                                    int width) {
  uint32_t register1 = 0;
  uint32_t significant_mask = 0xffffffff >> (32 - width);
  uint32_t register_msb_mask = 1 << (width - 1);
  uint32_t register_lsb_mask = 1;
  uint32_t byte_msb_mask = 0x80;

  for (int i = 0; i < length + (width / 8); ++i) {
    uint32_t byte = 0;

    if (i < length) byte = data[i];

    for (int j = 0; j < 8; ++j) {
      bool need_xor = ((register1 & register_msb_mask) == register_msb_mask);
      register1 <<= 1;

      bool need_or = ((byte & byte_msb_mask) == byte_msb_mask);
      byte <<= 1;

      if (need_or) register1 |= register_lsb_mask;
      if (need_xor) register1 ^= polynomial;
    }
  }

  return register1 & significant_mask;
}

int main(int argc, const char *argv[]) {
  uint8_t f[] = "123456789";
  uint32_t crc8 = 0x07;
  uint32_t crc16_xmodem = 0x1021;

  uint32_t crc = crc_bit_oriented_msb_first(f, sizeof(f) - 1, crc8, 8);
  printf("crc8 for \"%s\": \"%02X\"\n", f, crc);

  crc = crc_bit_oriented_msb_first(f, sizeof(f) - 1, crc16_xmodem, 16);
  printf("crc16 xmodem for \"%s\": \"%04X\"\n", f, crc);

  return 0;
}
crc8 for "123456789": "F4"
crc16 xmodem for "123456789": "31C3"

三、面向字节的CRC校验算法

1. 考察面向位的CRC求余

在这里插入图片描述
图示 ( 2 ) \color{red}{图示(2)} 图示(2)

  如上图所示,考察上面CRC求余的例子和按位求余算法,可以发现:

  • CRC求余过程在右边每移出8项,也就是1个字节后,所得的伪余式的前 m m m个有效项,是由移出字节后面的 m m m个有效项依次减去图中相应各行中灰色部分项而得。所以可以先依次计算相应各行中灰色部分项的和,再减去这个和。因为,加法、减法,与异或等同,也就是可以先计算灰色部分项异或,最后再与移出字节后面的 m m m项异或。
  • 而灰色部分项所在各行,表示当前移出这项系数是1,需要异或。这些整体上是由移出字节代表的项与 p ( x ) p(x) p(x)完全决定,而 p ( x ) p(x) p(x)是算法已知的部分,所以也相当于由移出字节所代表项完全决定。

  于是,只要把与每个字节相对应的灰色部分预先求出来,于是便能按1次移出1个字节来进行CRC求余。

2. 面向字节的CRC求余的数学原理

  现在来分析这个现象在数学上的原理:

  • 设当前伪余数为 r n ( x ) r_n(x) rn(x),设它的次数为 l l l,为了聚焦,可以只分析前 8 + m 8+m 8+m有效项,于是可以像下式这样表示 r n ( x ) r_n(x) rn(x)。其中 s ( x ) s(x) s(x) r n ( x ) r_n(x) rn(x)去掉前 8 + m 8+m 8+m有效项后的式子。
    r n ( x ) = a l x l + a l − 1 x l − 1 + ⋯ + a l − ( 8 + m − 1 ) x l − ( 8 + m − 1 ) + s ( x )    ( 12 ) r_n(x) = a_lx^l + a_{l-1}x^{l-1} + \cdots + a_{l-(8+m-1)}x^{l-(8+m-1)} + s(x) \space \space \color{red}{(12)} rn(x)=alxl+al1xl1++al(8+m1)xl(8+m1)+s(x)  (12)
  • 设当前伪余式经过移出1个字节的CRC求余后得到伪余式为 r m ( x ) r_m(x) rm(x),为了聚焦,可以只分析前 m m m个有效项,根据CRC求余算法,可知 r m ( x ) r_m(x) rm(x)去掉前 m m m个有效项后的式子也是 s ( x ) s(x) s(x)。于是可以像下式这样表示 r m ( x ) r_m(x) rm(x)
    r m ( x ) = b l − 8 x l − 8 + b l − 9 x l − 9 + ⋯ + b l − ( 8 + m − 1 ) x l − ( 8 + m − 1 ) + s ( x )    ( 13 ) r_m(x) =b_{l-8}x^{l-8} + b_{l-9}x^{l-9} + \cdots + b_{l-(8+m-1)}x^{l-(8+m-1)} + s(x) \space \space \color{red}{(13)} rm(x)=bl8xl8+bl9xl9++bl(8+m1)xl(8+m1)+s(x)  (13)
  • 根据CRC求余算法,有(14)式。(14)式中, q k ( x ) q_k(x) qk(x)次数是 l − m l-m lm,并满足(15)式。(15)式中, g ( x ) g(x) g(x)的次数为7。
    r n ( x ) = p ( x ) ⋅ q k ( x ) + r m ( x )    ( 14 ) r_n(x) = p(x) \cdot q_k(x) + r_m(x) \space \space \color{red}{(14)} rn(x)=p(x)qk(x)+rm(x)  (14)
    q k ( x ) = g ( x ) ⋅ x l − ( 8 + m ) + 1    ( 15 ) q_k(x) = g(x) \cdot x^{l-(8+m)+1} \space \space \color{red}{(15)} qk(x)=g(x)xl(8+m)+1  (15)
  • 将(12)、(13)和(15)式代入(14)式,有
    a l x l + a l − 1 x l − 1 + ⋯ + a l − ( 8 + m − 1 ) x l − ( 8 + m − 1 ) = p ( x ) ⋅ g ( x ) ⋅ x l − ( 8 + m ) + 1 + b l − 8 x l − 8 + b l − 7 x l − 7 + ⋯ + b l − ( 8 + m − 1 ) x l − ( 8 + m − 1 )    ( 16 ) a_lx^l + a_{l-1}x^{l-1} + \cdots + a_{l-(8+m-1)}x^{l-(8+m-1)} = p(x) \cdot g(x) \cdot x^{l-(8+m)+1} + b_{l-8}x^{l-8} + b_{l-7}x^{l-7} + \cdots + b_{l-(8+m-1)}x^{l-(8+m-1)} \space \space \color{red}{(16)} alxl+al1xl1++al(8+m1)xl(8+m1)=p(x)g(x)xl(8+m)+1+bl8xl8+bl7xl7++bl(8+m1)xl(8+m1)  (16)
  • (16)式两边乘以 x − ( l − ( 8 + m ) + 1 ) x^{-(l-(8+m)+1)} x(l(8+m)+1),也就是两边乘以 x − ( l − ( 8 + m − 1 ) ) x^{-(l-(8+m-1))} x(l(8+m1)),便有
    a l x ( 8 + m ) − 1 + a l − 1 x ( 8 + m ) − 2 + ⋯ + a l − ( 8 + m − 1 ) = p ( x ) ⋅ g ( x ) + b l − 8 x m − 1 + b l − 7 x m − 2 + ⋯ + b l − ( 8 + m − 1 )    ( 17 ) a_lx^{(8+m)-1} + a_{l-1}x^{(8+m)-2} + \cdots + a_{l-(8+m-1)} = p(x) \cdot g(x) + b_{l-8}x^{m-1} + b_{l-7}x^{m-2} + \cdots + b_{l-(8+m-1)} \space \space \color{red}{(17)} alx(8+m)1+al1x(8+m)2++al(8+m1)=p(x)g(x)+bl8xm1+bl7xm2++bl(8+m1)  (17)
  • 由域上的一元多项式环除法算式,可知有下式。其中, h ( x ) h(x) h(x)次数小于 m m m
    a l x ( 8 + m ) − 1 + a l − 1 x ( 8 + m ) − 2 + ⋯ + a l − 7 x m = p ( x ) ⋅ g ′ ( x ) + h ( x )    ( 18 ) a_lx^{(8+m)-1} + a_{l-1}x^{(8+m)-2} + \cdots + a_{l-7}x^m= p(x) \cdot g'(x) + h(x) \space \space \color{red}{(18)} alx(8+m)1+al1x(8+m)2++al7xm=p(x)g(x)+h(x)  (18)
  • (18)式代入(17)式,有
    p ( x ) ⋅ g ′ ( x ) + h ( x ) + a l − 8 x m − 1 + a l − 9 x m − 2 + ⋯ + a l − ( 8 + m − 1 ) = p ( x ) ⋅ g ( x ) + b l − 8 x m − 1 + b l − 7 x m − 2 + ⋯ + b l − ( 8 + m − 1 )    ( 19 ) p(x) \cdot g'(x) + h(x) + a_{l-8}x^{m-1} + a_{l-9}x^{m-2} + \cdots + a_{l-(8+m-1)} = p(x) \cdot g(x) + b_{l-8}x^{m-1} + b_{l-7}x^{m-2} + \cdots + b_{l-(8+m-1)} \space \space \color{red}{(19)} p(x)g(x)+h(x)+al8xm1+al9xm2++al(8+m1)=p(x)g(x)+bl8xm1+bl7xm2++bl(8+m1)  (19)
  • 两边都对 p ( x ) p(x) p(x)进行CRC求余,由于余数是唯一的,于是有
    h ( x ) + a l − 8 x m − 1 + a l − 9 x m − 2 + ⋯ + a l − ( 8 + m − 1 ) = b l − 8 x m − 1 + b l − 7 x m − 2 + ⋯ + b l − ( 8 + m − 1 )    ( 20 ) h(x) + a_{l-8}x^{m-1} + a_{l-9}x^{m-2} + \cdots + a_{l-(8+m-1)} = b_{l-8}x^{m-1} + b_{l-7}x^{m-2} + \cdots + b_{l-(8+m-1)} \space \space \color{red}{(20)} h(x)+al8xm1+al9xm2++al(8+m1)=bl8xm1+bl7xm2++bl(8+m1)  (20)

  于是,(18)、(20)式表明,当前伪余式经过移出1个字节的CRC求余后得到的伪余式的前 m m m有效项的系数,可以这样获得:

  1. 取移出的那个字节,先在其后补 m m m个0,设其式为 o ( x ) o(x) o(x),然后求出 o ( x ) o(x) o(x) p ( x ) p(x) p(x)的CRC余式 h ( x ) h(x) h(x)
  2. 取当前伪余式移出1个字节后的式子的前 m m m个有效项系数,再取上一步中所得余式 h ( x ) h(x) h(x)的所有有效项的系数,如果不足 m m m个,在前面补0系数,直至有 m m m个,对应异或,所得结果即是新伪余式的前 m m m有效项的系数。

3. 面向字节的CRC求余算法实现(C语言)

  根据上面的分析,可以如下构造面向字节的CRC求余算法,这里要求 f ( x ) f(x) f(x)的数据的有效项数是8的倍数。

  1. 如果 f ( x ) f(x) f(x)次数小于 m m m,也就是有效项数小于 m + 1 m+1 m+1
    f ( x ) f(x) f(x)便是要求的余式。如果 f ( x ) f(x) f(x)大于等于 m + 1 m+1 m+1位,取 f ( x ) f(x) f(x)的前 m m m个有效项到到暂存器,将待移入标记指向 f ( x ) f(x) f(x)剩下的项的第1个8项有效项。
  2. 暂存器在 f ( x ) f(x) f(x)上向右走8项,也就是把暂存器内容整体左移8个项,然后用待移入标记指向的8项有效项,补充暂存器最右边空出的8项,调整待移位标记指向 f ( x ) f(x) f(x)中下1个8项有效项。记住移出的8项的内容。
  3. 取上一步移出的8项的系数,在后面补充 m m m个0,构成一个 8 + m 8+m 8+m的二进制串,转换为一个模2剩余类域上的一元多项式,求出这个多项式对 p ( x ) p(x) p(x)的CRC余式。然后取这个余式的所有有效项的系数,如果不足 m m m个,就在前面补0系数,直到有 m m m个。这个 m m m个系数与暂存器中 m m m项系数异或,所得结果作为暂存器中的项的新系数。
  4. 重复上面的第2~3步,直到待移入标记不再指向 f ( x ) f(x) f(x)中的有效项,也就是 f ( x ) f(x) f(x)中所有的有效项都移入过暂存器。这时暂存器中的内容所代表的就是 f ( x ) f(x) f(x) p ( x ) p(x) p(x)的余式。

  下面给出适用于8位、16位和32位的MSB优先的面向字节的CRC求余算法的C语言代码,并使用上面的例子来测试。C语言代码及运行结果如下:

#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>


uint32_t crc_for_single_byte_msb_first(uint8_t data,
                                       uint32_t polynomial,
                                       int width) {
  uint32_t register1 = 0;
  uint32_t significant_mask = 0xffffffff >> (32 - width);
  uint32_t register_msb_mask = 1 << (width - 1);
  uint32_t register_lsb_mask = 1;
  uint32_t byte_msb_mask = 0x80;

  for (int i = 0; i < 1 + (width / 8); ++i) {
    uint32_t byte = 0;

    if (i < 1) byte = data;

    for (int j = 0; j < 8; ++j) {
      bool need_xor = ((register1 & register_msb_mask) == register_msb_mask);
      register1 <<= 1;

      bool need_or = ((byte & byte_msb_mask) == byte_msb_mask);
      byte <<= 1;

      if (need_or) register1 |= register_lsb_mask;
      if (need_xor) register1 ^= polynomial;
    }
  }

  return register1 & significant_mask;
}

uint32_t crc_mod_byte_oriented_msb_first(const uint8_t *data,
                                         int length,
                                         uint32_t polynomial,
                                         int width) {
  uint32_t register1 = 0;
  uint32_t significant_mask = 0xffffffff >> (32 - width);
  uint32_t register_msb_mask = 0xff << (width - 8);

  for (int i = 0; i < length; ++i) {
    uint32_t shift_out = (register1 & register_msb_mask) >> (width - 8);
    register1 = (register1 << 8) | data[i];
    uint32_t crc = crc_for_single_byte_msb_first(shift_out, polynomial, width);
    register1 ^= crc;
  }

  return register1 & significant_mask;
}

int main(int argc, const char *argv[]) {
  uint8_t f[] = "\xb1\x51\x10\x18";
  uint32_t p = 0x9101;

  uint32_t crc_mod = crc_mod_byte_oriented_msb_first(f, sizeof(f) - 1, p, 16);
  printf("\"%02X%02X%02X%02X\" mod \"%04X\" in crc way: \"%04X\"\n", f[0], f[1], f[2], f[3],
         p, crc_mod);

  return 0;
}
"B1511018" mod "9101" in crc way: "CA45"

4. 面向字节的CRC校验算法实现(C语言)

  需要注意,CRC校验发送端算法, f ( x ) f(x) f(x)是由上面的(2)式得到的,也就是对于待处理数据 D D D,要先在最后面附上 m m m位的0,再进行CRC求余。

  下面给出适用于8位、16位和32位的MSB优先的面向字节的CRC校验算法的C语言代码及运行结果如下:

#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>


uint32_t crc_for_single_byte_msb_first(uint8_t data,
                                       uint32_t polynomial,
                                       int width) {
  uint32_t register1 = 0;
  uint32_t significant_mask = 0xffffffff >> (32 - width);
  uint32_t register_msb_mask = 1 << (width - 1);
  uint32_t register_lsb_mask = 1;
  uint32_t byte_msb_mask = 0x80;

  for (int i = 0; i < 1 + (width / 8); ++i) {
    uint32_t byte = 0;

    if (i < 1) byte = data;

    for (int j = 0; j < 8; ++j) {
      bool need_xor = ((register1 & register_msb_mask) == register_msb_mask);
      register1 <<= 1;

      bool need_or = ((byte & byte_msb_mask) == byte_msb_mask);
      byte <<= 1;

      if (need_or) register1 |= register_lsb_mask;
      if (need_xor) register1 ^= polynomial;
    }
  }

  return register1 & significant_mask;
}

uint32_t crc_byte_oriented_msb_first(const uint8_t *data,
                                     int length,
                                     uint32_t polynomial,
                                     int width) {
  uint32_t register1 = 0;
  uint32_t significant_mask = 0xffffffff >> (32 - width);
  uint32_t register_msb_mask = 0xff << (width - 8);

  for (int i = 0; i < length + (width / 8); ++i) {
    uint32_t byte = 0;

    if (i < length) byte = data[i];

    uint32_t shift_out = (register1 & register_msb_mask) >> (width - 8);
    register1 = (register1 << 8) | byte;
    uint32_t crc = crc_for_single_byte_msb_first(shift_out, polynomial,
                                                 width);
    register1 ^= crc;
  }

  return register1 & significant_mask;
}

int main(int argc, const char *argv[]) {
  uint8_t f[] = "123456789";
  uint32_t crc8 = 0x07;
  uint32_t crc16_xmodem = 0x1021;

  uint32_t crc = crc_byte_oriented_msb_first(f, sizeof(f) - 1, crc8, 8);
  printf("crc8 for \"%s\": \"%02X\"\n", f, crc);

  crc = crc_byte_oriented_msb_first(f, sizeof(f) - 1, crc16_xmodem, 16);
  printf("crc16 xmodem for \"%s\": \"%04X\"\n", f, crc);

  return 0;
}
crc8 for "123456789": "F4"
crc16 xmodem for "123456789": "31C3"

四、基于表的面向字节的CRC校验算法

  上面的面向字节的算法相比于面向位的算法,虽然1次是按1个字节来操作,但是对单个字节的操作里又包含了位操作,所以实际上并没有减少运算。

  但是,对于每一种固定的算法,我们其实可以预先把对单个字节的256种值的CRC求余结果预先求得,并以字面量初始化形式,存在一个数组里,这样就可查表取值,相当于节省了这部分在运行时的的运算。

  下面给出适用于8位、16位和32位的MSB优先的基于表的CRC求余算法的C语言代码及运行结果如下:

#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>


uint32_t crc_for_single_byte_msb_first(uint8_t data,
                                       uint32_t polynomial,
                                       int width) {
  uint32_t register1 = 0;
  uint32_t significant_mask = 0xffffffff >> (32 - width);
  uint32_t register_msb_mask = 1 << (width - 1);
  uint32_t register_lsb_mask = 1;
  uint32_t byte_msb_mask = 0x80;

  for (int i = 0; i < 1 + (width / 8); ++i) {
    uint32_t byte = 0;

    if (i < 1) byte = data;

    for (int j = 0; j < 8; ++j) {
      bool need_xor = ((register1 & register_msb_mask) == register_msb_mask);
      register1 <<= 1;

      bool need_or = ((byte & byte_msb_mask) == byte_msb_mask);
      byte <<= 1;

      if (need_or) register1 |= register_lsb_mask;
      if (need_xor) register1 ^= polynomial;
    }
  }

  return register1 & significant_mask;
}

uint32_t crc_mod_table_based_msb_first(const uint8_t *data,
                                       int length,
                                       uint32_t polynomial,
                                       int width,
                                       const uint32_t table[256]) {
  uint32_t register1 = 0;
  uint32_t significant_mask = 0xffffffff >> (32 - width);
  uint32_t register_msb_mask = 0xff << (width - 8);

  for (int i = 0; i < length; ++i) {
    uint32_t shift_out = (register1 & register_msb_mask) >> (width - 8);
    register1 = ((register1 << 8) | data[i]) ^ table[shift_out];
  }

  return register1 & significant_mask;
}

int main(int argc, const char *argv[]) {
  uint8_t f[] = "\xb1\x51\x10\x18";
  uint32_t p = 0x9101;
  uint32_t table[256] = {};

  for (int i = 0; i < 256; ++i) {
    table[i] = crc_for_single_byte_msb_first(i, p, 16);
  }

  uint32_t crc_mod = crc_mod_table_based_msb_first(f, sizeof(f) - 1, p, 16,
                                                   table);
  printf("\"%02X%02X%02X%02X\" mod \"%04X\" in crc way: \"%04X\"\n", f[0], f[1], f[2], f[3],
         p, crc_mod);

  return 0;
}
"B1511018" mod "9101" in crc way: "CA45"

  下面给出适用于8位、16位和32位的MSB优先的基于表的CRC校验算法的C语言代码及运行结果如下:

#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>


uint32_t crc_for_single_byte_msb_first(uint8_t data,
                                       uint32_t polynomial,
                                       int width) {
  uint32_t register1 = 0;
  uint32_t significant_mask = 0xffffffff >> (32 - width);
  uint32_t register_msb_mask = 1 << (width - 1);
  uint32_t register_lsb_mask = 1;
  uint32_t byte_msb_mask = 0x80;

  for (int i = 0; i < 1 + (width / 8); ++i) {
    uint32_t byte = 0;

    if (i < 1) byte = data;

    for (int j = 0; j < 8; ++j) {
      bool need_xor = ((register1 & register_msb_mask) == register_msb_mask);
      register1 <<= 1;

      bool need_or = ((byte & byte_msb_mask) == byte_msb_mask);
      byte <<= 1;

      if (need_or) register1 |= register_lsb_mask;
      if (need_xor) register1 ^= polynomial;
    }
  }

  return register1 & significant_mask;
}

uint32_t crc_table_based_msb_first(const uint8_t *data,
                                   int length,
                                   uint32_t polynomial,
                                   int width,
                                   const uint32_t table[256]) {
  uint32_t register1 = 0;
  uint32_t significant_mask = 0xffffffff >> (32 - width);
  uint32_t register_msb_mask = 0xff << (width - 8);

  for (int i = 0; i < length + (width / 8); ++i) {
    uint32_t byte = 0;

    if (i < length) byte = data[i];

    uint32_t shift_out = (register1 & register_msb_mask) >> (width - 8);
    register1 = ((register1 << 8) | byte) ^ table[shift_out];
  }

  return register1 & significant_mask;
}

int main(int argc, const char *argv[]) {
  uint8_t f[] = "123456789";
  uint32_t crc8 = 0x07;
  uint32_t crc16_xmodem = 0x1021;
  uint32_t crc8_table[256] = {};
  uint32_t crc16_xmodem_table[256] = {};

  for (int i = 0; i < 256; ++i) {
    crc8_table[i] = crc_for_single_byte_msb_first(i, crc8, 8);
    crc16_xmodem_table[i] = crc_for_single_byte_msb_first(i, crc16_xmodem, 16);
  }

  uint32_t crc = crc_table_based_msb_first(f, sizeof(f) - 1, crc8, 8,
                                           crc8_table);
  printf("crc8 for \"%s\": \"%02X\"\n", f, crc);

  crc = crc_table_based_msb_first(f, sizeof(f) - 1, crc16_xmodem, 16,
                                  crc16_xmodem_table);
  printf("crc16 xmodem for \"%s\": \"%04X\"\n", f, crc);

  return 0;
}
crc8 for "123456789": "F4"
crc16 xmodem for "123456789": "31C3"

(由于平台对文章篇幅限制,此文分为上下两个部分,结尾部分请参阅《CRC校验算法的数学原理(下)》

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值