数学原理:模2除法、多项式表示法、生成多项式
好,咱们接着聊CRC的核心数学原理。说实话,我第一次接触CRC时,看到那些多项式、模2除法,头都大了。后来在项目里调了三天三夜的通讯,才真正搞明白——原来这东西没那么玄乎。
CRC校验的本质,说白了就是用除法算余数。只不过这个除法,不是咱们小学学的十进制除法,而是模2除法。你想想看,二进制世界里只有0和1,加法不进位,减法不借位,那除法自然也得入乡随俗。
模2除法:没有进位的世界
模2除法,也叫多项式除法。它的规则特别简单:
- 加减法都是异或运算(XOR),1+1=0,1-1=0
- 没有进位,没有借位
- 除法过程中,每一步只看当前最高位是不是1
举个例子,咱们算一下 1101 ÷ 11:
1001
11)1101
11
--
00
00
--
01
00
--
1 ← 余数
看到了吗?每一步都是异或,不是减法。我在项目中遇到过一位同事,用普通除法去算CRC,结果怎么都对不上。嗯,这就是踩坑了。
核心要点:模2除法 = 按位异或。记住这个,CRC就懂了一半。
多项式表示法:换个马甲而已
多项式表示法,其实就是把二进制数写成多项式的形式。比如:
- 二进制 1101 → 1·x³ + 1·x² + 0·x¹ + 1·x⁰ = x³ + x² + 1
- 二进制 1011 → x³ + x + 1
为什么非要这么搞?我个人习惯用多项式来理解CRC,因为生成多项式决定了CRC的检错能力。你想想看,不同的多项式就像不同的锁,有的锁好开,有的锁难撬。
这里有个小技巧:多项式的最高次幂,就是CRC的位数。比如 x³ + x + 1,最高次是3,那就是3位CRC,校验值范围0~7。
生成多项式:CRC的灵魂
生成多项式,就是CRC算法里那个固定的除数。不同的协议标准,用不同的多项式。我整理了一张表,都是工业现场常用的:
| CRC类型 | 生成多项式(二进制) | 生成多项式(十六进制) | 常见应用 |
|---|---|---|---|
| CRC-8 | 100000111 | 0x07 | 1-Wire、SMBus |
| CRC-16/IBM | 1000000000000101 | 0x8005 | Modbus RTU |
| CRC-16/CCITT | 10001000000100001 | 0x1021 | XMODEM、蓝牙 |
| CRC-32 | 100000100110000010001110110110111 | 0x04C11DB7 | Ethernet、ZIP |
我的经验:选生成多项式时,别自己发明。直接用标准协议里定义好的。我曾经见过一个团队自己编了个多项式,结果跟别人的设备死活对不上——通讯协议这东西,标准化才是王道。
CRC计算过程:手算一次就懂了
咱们拿CRC-8举个例子,生成多项式是 0x07(二进制 100000111)。假设要发送的数据是 0x53(二进制 01010011)。
步骤是这样的:
- 数据后面补0:CRC-8是8位,所以补8个0。数据变成 01010011 00000000
- 用生成多项式做模2除法:100000111 除 0101001100000000
- 余数就是CRC校验值
手算过程(简化版):
10110010 ← 商(我们不关心)
100000111)0101001100000000
100000111
---------
001000010
100000111
---------
000001010000
100000111
---------
000101110 ← 余数 = 0x5C
所以CRC-8校验值是 0x5C。发送时,把数据 0x53 和校验值 0x5C 一起发出去。接收端收到后,用同样的多项式再做一次除法,如果余数为0,说明数据没被篡改。
注意:不同协议对CRC的初始值、结果异或值、输入输出反转等细节有不同规定。比如Modbus RTU的CRC-16,初始值是0xFFFF,结果还要异或0x0000。这些细节在实现时一定要查协议文档,别想当然。
为什么CRC能检错?
这个问题我当年也琢磨了很久。其实道理很简单:
- 发送端算出的余数,相当于给数据加了个指纹
- 接收端重新算一遍,如果指纹对不上,说明数据在传输过程中变了
- 生成多项式选得好,就能检测出单比特错、双比特错、奇数个错、突发错
举个例子,CRC-16能检测出所有长度≤16的突发错误。这意味着什么?你想想看,工业现场电磁干扰再厉害,一般也就连续干扰几个比特。CRC-16基本能全兜住。
一句话总结:CRC就是用模2除法算余数,生成多项式就是除数的二进制表示。选对多项式,CRC就是工业通讯的守护神。
下一章咱们会聊CRC的代码实现,从查表法到直接计算法,我都会给出完整的Python代码。到时候你就能亲手写一个CRC校验函数了。