CRC8,CRC16,CRC32校验不可靠的概率有多高
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CRC8,CRC16,CRC32校验不可靠的概率有多高相关的知识,希望对你有一定的参考价值。
我做过测试,校验一百万个文件撞车了十来个,还是蛮高的。
参考技术A CRC即循环冗余校验码(Cyclic Redundancy Check):是数据通信领域中最常用的一种差错校验码,其特征是信息字段和校验字段的长度可以任意选定。循环冗余检查(CRC)是一种数据传输检错功能,对数据进行多项式计算,并将得到的结果附在帧的后面,接收设备也执行类似的算法,以保证数据传输的正确性和完整性本回答被提问者采纳CRC校验的概念及具体实现
概念
CRC(Cyclic redundancy check),循环冗余校验
CRC校验是用于检测一帧数据发送是否正确,只有确认对错的作用,并没有纠错的能力。
还有一点就是CRC校验通过了,并不代表这个数据肯定就是正确的,只能说尽可能减少出错的概率,当然
CRC错了那么这个数据肯定是不正确的。
而这个概率是跟CRC的位数相关,也跟选择的多项式有关,大致可以理解为CRC8,就是1/(28),CRC16则是1/(216)以此类推。
对于检验一帧数据是否正确有很多算法,CRC只是其中的一种,SUM的形式也可以的,只是算法不同对于校验结果的效果也是不一样的,最好的效果是,每一位的变化都可以引起最终checksum的值发生较大的改变。引入除法计算是一种很好的方法,每一位发生改变对于最后的余数都会引起较大的变化。
多项式(Polynomical)
多项式即CRC除法的除数,而且多项式是总于高于CRCN中N的一位,这样可以保证余数的位数与N相同。同时多项式也有好坏之分,区别就是在于出错的概率,至于哪种多项式好一些,这个一般来说是数学家的事情,我们工程上拿过来用就好,而且一般的协议中也已经规定了这个CRC的多项式。
其实多项式只是一种表现方式,当然也可以直接用16进制表示
以CRC-CCITT为例
也可以表示为0x1021
计算例子
引用别人文档中的例子来说明CRC机制,如下是一个CRC4计算的例子
1100001010 = Quotient (nobody cares about the quotient)
_______________
10011 ) 11010110110000 = Augmented message (1101011011 + 0000)
=Poly 10011,,.,,....
-----,,.,,....
10011,.,,....
10011,.,,....
-----,.,,....
00001.,,....
00000.,,....
-----.,,....
00010,,....
00000,,....
-----,,....
00101,....
00000,....
-----,....
01011....
00000....
-----....
10110...
10011...
-----...
01010..
00000..
-----..
10100.
10011.
-----.
01110
00000
-----
1110 = Remainder = THE CHECKSUM!!!!
先将要计算的后方填充相应位数的0(CRC4,4位),再对POLY进行求余操作,这个余数就是我们要的checksum
这个操作就是一个除法操作,只是在减的时候用XOR来代替减法,这样就不要考虑进位借位的问题,而且XOR来代替减法也不会使CRC的效果变差,因为每一位的改变还是会引起checksum较大的变化。
计算方法
了解了CRC的原理,接下来就对CRC校验进行计算,接下来的讨论都以CRC16为模板。
一、直接计算法
这个方法就是根据CRC的定义,进行按位操作,先将数据移位,若高位是1的话,就将当前的CRC与poly进行XOR操作来更新当的CRC值,直至所有的数据被更新后,再在数据的结尾添加相应的0位来得到最终的CRC结果,对于CRC16来说这个零位就是2个字节。总体来说,这种实现方式是根据定义来的,比较好理解,不过运行速度有待提高。
示例代码如下:
unsigned int poly = 0x11021;
unsigned char testData[10] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa};
unsigned short crcUpdate(unsigned short crc, unsigned char data)
{
int i;
unsigned short result;
unsigned int tmp;
tmp = crc;
for(i = 0; i < 8; i++)
{
tmp <<= 1;
if(data & 0x80)
{
tmp += 1;
}
data <<= 1;
if(tmp & 0x10000)
{
tmp ^= poly;
}
}
result = tmp &0xffff;
return result;
}
unsigned short crcCheck(unsigned char *pData, unsigned char size)
{
unsigned short result = 0;
int i;
for(i = 0; i < size; i++)
{
result = crcUpdate(result, *(pData+i));
}
//补零
for(i = 0; i < 2; i++)
{
result = crcUpdate(result, 0);
}
return result;
}
void demo(void)
{
printf("Result %04x
",crcCheck(testData,10)); //print 0xd877
}
二、表驱动法(Table-Driven Implementation)
在直接计算的情况下,为了提高运行的速度别人又提出了用表驱动的方法(根据当前的值来查找相应的CRC的结果,再代入公式进行计算最终的结果)。换句话,直接计算是通过一次移一位的操作来进行,而表驱动法刚是采用一次移多位的形式来进行CRC计算,来提高运行速度。
原理XOR也是满足交换律如下
根据这个交换律,我们可以先将POLY进行移位XOR,再将结果同最初的值来进行XOR,来得来相应的移位。
下面以CRC8来说明
//以1位为单位为进行XOR
1 0 1 1 1 0 0 0
________________________________
1 0 0 0 1 1 1 0 0/ 1 0 1 1 0 1 0 0 0 0 0 0 0 0 0 0
1 0 0 0 1 1 1 0 0
0 0 0 0 0 0 0 0 0
1 0 0 0 1 1 1 0 0
1 0 0 0 1 1 1 0 0
1 0 0 0 1 1 1 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
-------------------------------
1 0 1 0 0 0 0 0
//先将POLY的多次移位进行XOR
1 0 0 0 1 1 1 0 0
0 0 0 0 0 0 0 0 0
1 0 0 0 1 1 1 0 0
1 0 0 0 1 1 1 0 0
1 0 0 0 1 1 1 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
-------------------------------
(1 0 1 1 0 1 0 0)1 0 1 0 0 0 0 0
相当于是这样次一次性移位8位,8位的值为(10110100)其对于应是这样的操作,后8位直接XOR(10100000)
这个其实跟直接除xor是一样的,只是这样一次移位得多,可以加快计算结果。
表的生成
表的长度
是由一次移动的位数决定,如一次性移4bit,那么表的长度就是24(16),如果一次性移8bit(1byte),那么表的长度就是28(256),一般都是256为长度。
表的一个单元
这个就是由CRCN,这个N来决定,CRC8为8bit,CRC16为16bit
表的生成代码
unsigned short crc16Table[256] = {0};
unsigned short poly = 0x8005;
void crcTableCreate(void)
{
int i = 0;
for(i = 0; i < 256; i++)
{
unsigned short crc;
crc = i << 8;
for(int j = 0; j < 8; j++)
{
if(crc & 0x8000)
{
crc = (crc << 1) ^ poly;
}
else
{
crc <<= 1;
}
}
crc16Table[i] = crc;
}
}
//crc16Table
0000,8005,800f,000a,801b,001e,0014,8011,8033,0036,003c,8039,0028,802d,8027,0022,
8063,0066,006c,8069,0078,807d,8077,0072,0050,8055,805f,005a,804b,004e,0044,8041,
80c3,00c6,00cc,80c9,00d8,80dd,80d7,00d2,00f0,80f5,80ff,00fa,80eb,00ee,00e4,80e1,
00a0,80a5,80af,00aa,80bb,00be,00b4,80b1,8093,0096,009c,8099,0088,808d,8087,0082,
8183,0186,018c,8189,0198,819d,8197,0192,01b0,81b5,81bf,01ba,81ab,01ae,01a4,81a1,
01e0,81e5,81ef,01ea,81fb,01fe,01f4,81f1,81d3,01d6,01dc,81d9,01c8,81cd,81c7,01c2,
0140,8145,814f,014a,815b,015e,0154,8151,8173,0176,017c,8179,0168,816d,8167,0162,
8123,0126,012c,8129,0138,813d,8137,0132,0110,8115,811f,011a,810b,010e,0104,8101,
8303,0306,030c,8309,0318,831d,8317,0312,0330,8335,833f,033a,832b,032e,0324,8321,
0360,8365,836f,036a,837b,037e,0374,8371,8353,0356,035c,8359,0348,834d,8347,0342,
03c0,83c5,83cf,03ca,83db,03de,03d4,83d1,83f3,03f6,03fc,83f9,03e8,83ed,83e7,03e2,
83a3,03a6,03ac,83a9,03b8,83bd,83b7,03b2,0390,8395,839f,039a,838b,038e,0384,8381,
0280,8285,828f,028a,829b,029e,0294,8291,82b3,02b6,02bc,82b9,02a8,82ad,82a7,02a2,
82e3,02e6,02ec,82e9,02f8,82fd,82f7,02f2,02d0,82d5,82df,02da,82cb,02ce,02c4,82c1,
8243,0246,024c,8249,0258,825d,8257,0252,0270,8275,827f,027a,826b,026e,0264,8261,
0220,8225,822f,022a,823b,023e,0234,8231,8213,0216,021c,8219,0208,820d,8207,0202,
unsigned short crcUpdate2(unsigned short crcIn, unsigned char data)
{
unsigned short result = 0;
result = (crcIn << 8 | data) ^ crc16Table[(crcIn >> 8) & 0xff];
return result;
}
unsigned short crcCheck2(unsigned char *pData, unsigned char size)
{
unsigned short crcResult = 0;//Initial Value
for(int i = 0; i < size; i++)
{
crcResult = crcUpdate2(crcResult, *(pData+i));
}
// add zero to the tail
for(int i = 0; i < 2; i++)
{
crcResult = crcUpdate2(crcResult, 0);
}
return crcResult;
}
void demo(void)
{
crcTableCreate();
printf("Result %04x
",crcCheck2(testData,10)); //print 0x2a62
}
三、直驱表法(Slightly Mangled Table-Driven Implementation)
直驱表法这种翻译说法,我也不知道是否合理,照我个理解来说,这个应该叫做一种表驱动法的变种
这个变种作用是在于在进行CRC计算之后不需要进行填充相应位数的0。
驱动表还是一样的,只是计算的公式不一样
unsigned short crcUpdate3(unsigned short crcIn, unsigned char data)
{
unsigned short result = 0;
result = (crcIn << 8) ^ crc16Table[(crcIn >> 8) ^ data];
return result;
}
unsigned short crcCheck3(unsigned char *pData, unsigned char size)
{
unsigned short crcResult = 0;//Initial Value
for(int i = 0; i < size; i++)
{
crcResult = crcUpdate3(crcResult, *(pData+i));
}
return crcResult;
}
void demo(void)
{
crcTableCreate();
printf("Result %04x
",crcCheck3(testData,10)); //print 0x2a62
}
这个做法是这样的,至于这个公式是如何推导出来的,看的资料也没有解释清楚的,本人暂时也还不太理解,不过实际工程上基本都用的是这个方法。
而实际表驱动法与直驱表法,其实就是两种算法,虽然两者可以用的是相同的表,只有在驱动表法的初始值为0与填充值为0才与驱动表法初始值为0的情况下结果是一样的,其他情况下值应该都是不一样的。
对于CRC的真正用途来说,算法没有具体的意义,只是有在数据发生改变的时候,CHECKSUM就可以发生较大的改变,且重复的概率比较小,那么这种算法就是一种比较好的算法。工程上用这种算法,就可以省掉补零的操作。
CRC的其他述语
上面说的是CRC的基本概念以及实现的方式,而在实际的用途中这个CRC还是会一些细微的参数。
如下是一些CRC的表述方式
Name : "CRC-16/CITT"
Width : 16
Poly : 1021
Init : FFFF
RefIn : False
RefOut : False
XorOut : 0000
Check : ?
Name : "XMODEM"
Width : 16
Poly : 8408
Init : 0000
RefIn : True
RefOut : True
XorOut : 0000
Check : ?
Name : "ARC"
Width : 16
Poly : 8005
Init : 0000
RefIn : True
RefOut : True
XorOut : 0000
Check : ?
标识 | 含义 |
---|---|
Name | 名字标识 |
Width | CRC长度 |
Poly | 多项式 |
Init | 寄存器初始值,针对直驱表法的寄存器初始值 |
RefIn | 输入是否是低位在前 |
RefOut | 输出是否低位在前 |
XorOut | 输出前与这个值进行XOR再输出 |
Check | 表示的一个参考输出,以STRING(123456789)为例 |
这里的参数无论哪一个修改了,最终的值都会发生变化,相当于就生成一个新的CRC校验方式
网上写CRC的文章很多,记录自己的理解前也参考了很多的文章
主要参考如下几篇文章
我学习 CRC32、CRC16、CRC 原理和算法的总结(与 WINRAR 结果一致)
A PAINLESS GUIDE TO CRC ERROR DETECTION ALGORITHMS
以上是关于CRC8,CRC16,CRC32校验不可靠的概率有多高的主要内容,如果未能解决你的问题,请参考以下文章