我有一个关于CRC计算的数学和程序设计相关问题,以避免在只需要更改一小部分时避免重新计算一个块的完整CRC的问题。
我的问题如下:我有一个4字节结构的1K块,每个块代表一个数据字段。完整的1K块的末尾有一个CRC16块,以完整的1K计算。当我只需要更改4字节结构时,我应该重新计算完整块的CRC,但是我正在寻找一种更有效的解决方案。某处:
我使用了完整的1K块电流CRC16
我在旧的4字节块上计算内容
我“减去”了在步骤2中从完整的1K CRC16中获得的内容
我在新的4字节块上计算内容
我将在第4步中获得的内容“添加”到在第3步中获得的结果
总而言之,我正在考虑这样的事情:
CRC(新满)= [CRC(旧满)-CRC(旧块)+ CRC(新块)]
但是考虑到“通用公式”,我却缺少背后的数学以及如何获得此结果。
谢谢。
答案 0 :(得分:3)
采用您最初的1024字节块A和新的1024字节块B。异或-或它们得到块C。由于您仅更改了四个字节,所以C将是一堆零,四个字节是互斥的-或之前和新的四个字节,以及更多的零。
现在计算块C的CRC-16,但无需任何预处理或后处理。我们将其称为CRC-16'。 (我需要查看您正在使用的特定CRC-16,以查看该处理是什么,如果有的话。)异或-或将A块的CRC-16与C块的CRC-16'结合使用,现在您有了B块的CRC-16。
乍一看,与仅计算块B的CRC相比,这似乎没有太大的收获。但是,有一些技巧可以快速计算出一堆零的CRC。首先,在更改的四个字节之前的零位 给出的CRC-16'为零,而不管有多少个零。因此,您只需使用先前和新的四个字节的异或开始计算CRC-16'。现在,您将继续在更改后的字节后的其余 n 个零上计算CRC-16'。通常,在 n 个字节上计算CRC需要O( n )时间。但是,如果您知道它们全为零(或所有某个常数值),则可以在O(log n )时间中进行计算。您可以在zlib's crc32_combine()
routine中看到有关此操作的示例,并将其应用于您的CRC。
鉴于您的CRC-16 / DNP参数,下面的zeros()
例程将在O(log n )时间将请求的零字节数应用于CRC。
// Return a(x) multiplied by b(x) modulo p(x), where p(x) is the CRC
// polynomial, reflected. For speed, this requires that a not be zero.
uint16_t multmodp(uint16_t a, uint16_t b) {
uint16_t m = (uint16_t)1 << 15;
uint16_t p = 0;
for (;;) {
if (a & m) {
p ^= b;
if ((a & (m - 1)) == 0)
break;
}
m >>= 1;
b = b & 1 ? (b >> 1) ^ 0xa6bc : b >> 1;
}
return p;
}
// Table of x^2^n modulo p(x).
uint16_t const x2n_table[] = {
0x4000, 0x2000, 0x0800, 0x0080, 0xa6bc, 0x55a7, 0xfc4f, 0x1f78,
0xa31f, 0x78c1, 0xbe76, 0xac8f, 0xb26b, 0x3370, 0xb090
};
// Return x^(n*2^k) modulo p(x).
uint16_t x2nmodp(size_t n, unsigned k) {
k %= 15;
uint16_t p = (uint16_t)1 << 15;
for (;;) {
if (n & 1)
p = multmodp(x2n_table[k], p);
n >>= 1;
if (n == 0)
break;
if (++k == 15)
k = 0;
}
return p;
}
// Apply n zero bytes to crc.
uint16_t zeros(uint16_t crc, size_t n) {
return multmodp(x2nmodp(n, 3), crc);
}
答案 1 :(得分:2)
CRC实际上使这很容易。
研究这个问题时,我确定您已经开始阅读CRC,它是使用GF(2)上的多项式来计算的,并且可能会跳过该部分而直接获得有用的信息。好吧,听起来好像是您该重新浏览这些内容并重新阅读几次以使您真正理解它的时候了。
但是无论如何...
由于CRC的计算方式,它们具有以下属性:给定两个块A和B,CRC(A xor B)= CRC(A)xor CRC(B)
因此,您可以做的第一个简化就是只需要计算已更改位的CRC。实际上,您可以预先计算该块中每个位的CRC,这样,当您更改某个位时,只需将其CRC异或到该块的CRC中即可。
CRC还具有CRC(A * B)= CRC(A * CRC(B))的特性,其中*是GF(2)的多项式乘法。如果您将块的末尾填充为零,则不要对CRC(B)执行此操作。
这使您可以使用较小的预先计算的表。 “基于GF(2)的多项式乘法”是二进制卷积,因此乘以1000等于移位3位。使用此规则,您可以预先计算每个字段的偏移量的CRC。然后,只需将更改后的位乘以偏移CRC(无零填充计算)即可,计算出这8个字节的CRC,然后将它们异或为块CRC。
答案 2 :(得分:2)
CRC是由输入流形成的长整数和与多项式相对应的短整数p
的剩余部分。
如果您在中间更改一些位,这将导致n 2^k
的分频扰动,其中n
具有被扰动部分的长度,而k
是位数随后。
因此,您需要计算余数(n 2^k) mod p
的扰动。您可以使用
(n 2^k) mod p = (n mod p) (2^k mod p)
第一个因素就是n
的CRC16。通过平方的幂算法可以在Log k
操作中有效地获得另一个因素。
答案 3 :(得分:0)
CRC取决于之前数据的计算CRC。 因此,唯一的优化是将数据逻辑划分为N个段,并为每个段存储计算出的CRC状态。
然后,例如修改(0..9的)段6,获得段5的CRC状态,并继续计算从段6开始到9结束的CRC。
无论如何,CRC计算非常快。所以想想,如果值得的话。