二、多项式选择与生成

做CRC,第一个绕不开的问题就是:用哪个多项式?

我刚开始接触CRC的时候,觉得随便找一个多项式用就行了。后来在项目里吃过亏——同样的数据,换了一个多项式,误码检测结果天差地别。从那以后,我才认真去研究多项式背后的门道。

2.1 常见生成多项式

业内已经积累了一批经过验证的多项式。我把最常用的几个列在下面:

名称多项式(二进制表示)十六进制典型应用场景
CRC-81000001110x071-Wire、SMBus
CRC-16-IBM110000000000001010x8005Modbus、USB
CRC-16-CCITT100010000001000010x1021XMODEM、蓝牙
CRC-321000001001100000100011101101101110x04C11DB7以太网、ZIP、PNG
CRC-64-ECMA(太长)0x42F0E1EBA9EA3693大文件校验、分布式存储

你可能会问:为什么同样是16位,IBM和CCITT不一样?

说白了,它们被设计出来的时候目标不同。IBM侧重硬件查表效率,CCITT侧重对抗突发错误。我真实遇到过一个案例:某个老系统的通信协议用了CRC-16-IBM,后来要升级兼容性,新设备只有CCITT的硬件加速器,结果两边算出来的校验码对不上——硬件只能重新改。所以选多项式之前,一定要看看生态里别人用什么。

2.2 多项式的关键性质

多项式不是随便凑出来的。判断一个多项式好不好,主要看三点:

  • 本原性:多项式不能被分解成更小的因式。说白了,它能生成最大周期的伪随机序列,保证覆盖所有可能的位错误模式。
  • 汉明距离:在给定数据长度下,能检测出的最小错误位数。比如CRC-32对最多32768位数据,汉明距离为4,意味着至少4位错误才会漏检。
  • 生成项权重:多项式中非零项的个数。权重越高,通常对抗突发错误的能力越强。但权重太高会加大硬件运算量。

核心指标:对于工程实践,我最看重的两个参数是——可检测突发长度和漏检率。一般多项式能检测长度不超过校验位数的所有突发错误。例如CRC-16可以保证检测出≤16位的连续错误。

2.3 选择原则

每次有同事问我“用哪个多项式”,我都会反问三个问题:

  1. 你的数据包最大有多长? 数据越长,对多项式的汉明距离要求越高。CRC-8用在几百字节的传感器数据上可以,但用在几兆字节的文件上就容易漏检。
  2. 通讯信道的主要噪声类型是什么? 如果是高斯白噪声,随便一个标准多项式都够用。但如果是工业环境那种强烈的电磁脉冲(突发错误长),就要选能覆盖较长突发错误的多项式。
  3. 硬件还是软件实现? 软件实现讲究查表效率,多项式阶数不能太大,16位以内比较划算;硬件实现更灵活,32位甚至64位都不成问题。

嗯,这里要注意一个坑:不要自己发明多项式。

我曾在一个SDK里看到团队自己拍脑袋写了个0x1031,结果测试时发现某些连续4位错误检测不出,后来查资料才知道他们选的多项式其实是0x1021的变形,但少了两个项,导致汉明距离直接降了1。标准多项式之所以叫标准,是经过了数学家、通信工程师几十年的验证。我自己从来不造轮子,直接从IEEE、ITU的推荐列表里选。

2.4 多项式对应的生成算法

选好多项式之后,怎么用代码生成CRC?拿CRC-32举个例子:

// Python 示例:查表法生成 CRC-32
def crc32(data, poly=0x04C11DB7):
    crc = 0xFFFFFFFF
    for byte in data:
        crc ^= (byte << 24)
        for _ in range(8):
            if crc & 0x80000000:
                crc = (crc << 1) ^ poly
            else:
                crc = (crc << 1)
        crc &= 0xFFFFFFFF
    return crc ^ 0xFFFFFFFF

代码里做了两件事:初始值全1、输出异或全1。这两个参数也是影响兼容性的关键。不同标准对初始值和输出异或值有不同规定,比如CRC-32CCITT就用0x0初始值。选多项式的时候,必须一起确认这些配套参数。

经验:写测试脚本时,先用已知的“金标准”验证一下。比如CRC-32的预期值是0xCBF43926,你可以拿一个空字符串去算,看对不对得上。我每次移植CRC库都会先跑这个用例。

2.5 知识体系总览

下面这张图把多项式选择的核心脉络串起来了:

多项式选择与生成知识体系 CRC 多项式 常见多项式列表 CRC-8 / CRC-16-IBM CRC-16-CCITT / CRC-32 CRC-64-ECMA 关键性质 本原性 汉明距离 生成项权重 选择原则 数据包长度 信道噪声类型 实现方式 (硬件/软件) CRC 生成算法 查表法 / 直接计算法

警告:千万不要把多项式写成十六进制时搞反位序。比如0x1021是左对齐还是右对齐?不同参考文档可能有歧义。我遇到过两个团队对接,一个按低字节在前,一个按高字节在前,结果谁都说不清。建议在接口文档里明确写出“本协议使用CRC-16-CCITT,左对齐,初始值0x0000,输出异或0x0000”。

选多项式这件事,貌似是第一步,其实决定了整个CRC校验的可靠性。标准的多项式是经过大量理论证明和现场验证的,直接拿来用,成本最低、风险最小。个人经验是:优先用你所在行业的标准组织推荐的,如果行业没规定,就用CRC-16-CCITT或者CRC-32,这两个通用性最好。