1. 简介
CRC即循环冗余校验。是一种来验证数据是否出现差错的一种算法
2. 引入
如何检验一串数据出现了差错呢?
2.1 加法
可以将所有这些数据分块相加然后再模上一个数,得到最后的checksum
。
但是这样做的缺点是,可能值已经改变了多次,但是checksum
却相同。
如:
数据 24 23 ,checksum
为47
但若数据变为 25 22,checksum
却并没有发生改变。
所以用累加求和的方式来检验数据的差错性并不理想。
CRC处理差错的方式是除法
2.2 除法
再计算机我们用二进制来表示一个数;
为了方便我们计算,我们直接忽略掉产生的最高位进位。
这样对于二进制的加减法,我们都可以用异或来进行计算。
做了这样操作后,实际上是没有大小这一说了。
而对于乘法,则是一个数循环左移最后再进行上述加法的过程。
同样为了方便计算,我们做除法时只考虑最高位是否大于除数即可。
2.3 除数的选择
除数的选择,这些都是数学家们的事。
他们有个名字叫做生成多项式。
这个多项式具体如何选择能取得更好的效果,也是数学家的事情。
我们要产生的是二进制的checksum
,自然多项式的形式应该为
∑ i = 0 n v i ∗ 2 i , v i = 0 , 1 \sum_{i=0}^{n}v_i *2^{i},v_i = 0,1 ∑i=0nvi∗2i,vi=0,1
如果你对如何选择多项式不感兴趣可以直接看实现上,可以直接跳到实现上。
假设原来的数据为
D
D
D,若出现差错了;则差错序列为
E
E
E,则一定存在一个序列
T
T
T使得
D
⊕
T
=
E
D \oplus T =E
D⊕T=E
且序列
D
!
=
0
D != 0
D!=0,一定发生了差错。
为了能够检测到错误,生成多项式
P
P
P一定不能被
D
D
D二进制整除(上面所叙述的)。
那么有这么几条:
- 为了检测单个比特位的改变,只需要
P
P
P中二进制表示中
1
的位数超过1即可。
因为无论 P P P怎样进行循环左移,最后的二进制表示中1
的位数都一定是超过1
的,所以能检测到单个比特位发生的变化 - 为了检测两个比特位的改变,
P
P
P的形式不能为
11
101
1001...
11 \ 101 \ 1001 ...
11 101 1001...的倍数(循环左移)。
这个也很好理解,假设原串的两个比特位为0
, 现在都变为了1
,而这两个1
的距离又刚好与 P P P中的两个1
的距离一样不就使得 P ∣ E P | E P∣E了吗。 - 奇数个二进制的
1
位的 E E E一定不能被偶数个二进制位1
的 P P P所整除,即可以检测到。 - 一堆连续的
1
的差错出现可以使得 P P P的最低位为1来解决。
根据这几个原则,最后选择的多项式
16 12 5 0
16 15 2 0
32 26 23 22 16 12 11 10 8 7 5 4 2 1 0
3. 实现
3.1 简单实现
while (len--)
{
byte t = (r >> 24) & 0xFF;
r = (r << 8) | *p++;
r^=table[t];
}
//
r=0; while (len--) r = ((r << 8) | *p++) ^ t[(r >> 24) & 0xFF];
3.2 标准实现
for (unsigned int i = 0; i <= 0xFF; i++)
{
uint32_t crc = i;
for (unsigned int j = 0; j < 8; j++)
crc = (crc >> 1) ^ (-int(crc & 1) & Polynomial);
Crc32Lookup[i] = crc;
}
uint32_t crc32_1byte(const void* data, size_t length, uint32_t previousCrc32 = 0)
{
uint32_t crc = ~previousCrc32;
unsigned char* current = (unsigned char*) data;
while (length--)
crc = (crc >> 8) ^ Crc32Lookup[(crc & 0xFF) ^ *current++];
return ~crc;
}
3.3 现代cpu优化
4字节或者8字节处理一次
// same as before
for (unsigned int i = 0; i <= 0xFF; i++)
{
uint32_t crc = i;
for (unsigned int j = 0; j < 8; j++)
crc = (crc >> 1) ^ ((crc & 1) * Polynomial);
Crc32Lookup[0][i] = crc;
}
for (unsigned int i = 0; i <= 0xFF; i++)
{
// for Slicing-by-4 and Slicing-by-8
Crc32Lookup[1][i] = (Crc32Lookup[0][i] >> 8) ^ Crc32Lookup[0][Crc32Lookup[0][i] & 0xFF];
Crc32Lookup[2][i] = (Crc32Lookup[1][i] >> 8) ^ Crc32Lookup[0][Crc32Lookup[1][i] & 0xFF];
Crc32Lookup[3][i] = (Crc32Lookup[2][i] >> 8) ^ Crc32Lookup[0][Crc32Lookup[2][i] & 0xFF];
// only Slicing-by-8
Crc32Lookup[4][i] = (Crc32Lookup[3][i] >> 8) ^ Crc32Lookup[0][Crc32Lookup[3][i] & 0xFF];
Crc32Lookup[5][i] = (Crc32Lookup[4][i] >> 8) ^ Crc32Lookup[0][Crc32Lookup[4][i] & 0xFF];
Crc32Lookup[6][i] = (Crc32Lookup[5][i] >> 8) ^ Crc32Lookup[0][Crc32Lookup[5][i] & 0xFF];
Crc32Lookup[7][i] = (Crc32Lookup[6][i] >> 8) ^ Crc32Lookup[0][Crc32Lookup[6][i] & 0xFF];
}