我正在尝试实现8位fletcher校验和功能。
我的数据总是长达17个字节。
我从Remake of Fletcher checksum from 32bit to 8
的代码开始以下是我最终拥有的内容:
// 8-bit Fletcher checksum
// data is always 17 byte long
uint8_t fletcher(uint8_t *data) {
uint8_t sum1 = 0x0f, sum2 = 0x0f, len = 17;
while(len) {
sum1 += *data++;
sum2 += sum1;
sum1 = (sum1 & 0x0f) + (sum1 >> 4);
sum2 = (sum2 & 0x0f) + (sum2 >> 4);
len--;
}
sum1 = (sum1 & 0x0f) + (sum1 >> 4);
sum2 = (sum2 & 0x0f) + (sum2 >> 4);
return sum2<<4 | sum1;
}
我想知道它是否好,而且我的印象是我可以进一步简化,但我找不到哪里(也许它毕竟不能进一步简化......)。
我的主要问题是这段代码看起来是否可行。我在基于无线的数据链路的两侧使用它将“工作”(从相同的数据返回相同的校验和)但在fletcher方式可能是错误的,并且不提供预期的错误检测...
希望我足够清楚......
提前致谢!
答案 0 :(得分:4)
首先,Fletcher的任何变体都不是CRC。这是一个校验和。
其次,只要算法正确,您处理的字节数就没有什么特别之处。
第三,你不需要也不应该在每一步都做模数15。对于速度(与CRC相比,这是Fletcher和的整点),应该有一个仅由sum2 += sum1 += *data++;
组成的内环。根据{{1}}和sum1
数据类型的大小,您可以计算在溢出sum2
之前可以执行的迭代次数,假设所有输入字节都是sum2
。然后一个外部循环运行该内部循环多次,然后是两个模数15。外部循环遍历所有输入数据。
第四,当0xff
最终为15时,(x >> 4) + (x & 0xf)
操作无法完成预期的x % 15
。需要有一个最终x
。
更新
好的,所以这是代码:
if (x == 15) x = 0;
请注意,我在#include <stdio.h>
#define MAXPART 5803 /* for 32-bit unsigned sum1, sum2 */
/* #define MAXPART 22 for 16-bit unsigned sum1, sum2 */
unsigned fletcher8(unsigned f8, unsigned char *data, size_t len)
{
unsigned long sum1, sum2;
size_t part;
sum1 = f8 & 0xf;
sum2 = (f8 >> 4) & 0xf;
while (len) {
part = len > MAXPART ? MAXPART : len;
len -= part;
do {
sum2 += sum1 += *data++;
} while (--part);
sum1 %= 15;
sum2 %= 15;
}
return (sum2 << 4) + sum1;
}
#define SIZE 131072
int main(void)
{
unsigned f8 = 1;
unsigned char buf[SIZE];
size_t got;
while ((got = fread(buf, 1, SIZE, stdin)) > 0)
f8 = fletcher8(f8, buf, got);
printf("0x%02x\n", f8);
return 0;
}
而不是1
处启动了Fletcher8值。这足以确保任何长度的零的字符串将产生相同的零结果。您可以将初始值设置为您喜欢的任何值,只要它不是零。
如果机器上的模(0xff
)运算非常慢,那么使用一组移位和加法进行%
运算可能会更快。以下是32位类型的示例:
% 15
对于k = (k >> 16) + (k & 0xffff);
k = (k >> 8) + (k & 0xff);
k = (k >> 4) + (k & 0xf);
k = (k >> 4) + (k & 0xf);
if (k > 14)
k -= 15;
所述的情况,代码可以简化为使用len == 17
代替unsigned
unsigned long
和sum1
,并跳过自sum2
以来的外循环。非%模运算也可以缩短。这就是我在循环中消除了不必要的减量操作的地方:
len <= 22
为了进行比较,您可以尝试这个8位CRC,看看它如何比较速度(crc应初始化为零):
unsigned fletch8_17(unsigned char *data)
{
unsigned sum1 = 1;
unsigned sum2 = 0;
unsigned char *end = data + 17;
do {
sum2 += sum1 += *data++;
} while (data < end);
sum1 = (sum1 >> 8) + (sum1 & 0xff);
sum1 = (sum1 >> 4) + (sum1 & 0xf);
if (sum1 > 14) {
sum1 -= 15;
if (sum1 > 14)
sum1 -= 15;
}
sum2 = (sum2 >> 8) + (sum2 & 0xff);
sum2 = (sum2 >> 4) + (sum2 & 0xf);
if (sum2 > 14) {
sum2 -= 15;
if (sum2 > 14)
sum2 -= 15;
}
return (sum2 << 4) + sum1;
}
8位CRC肯定会比8位Fletcher校验和提供更好的错误检测性能。在这种情况下甚至可能更快!
答案 1 :(得分:2)
您的代码看起来很像Fletcher校验和中维基百科文章的Optimizations section中给出的代码。我猜your source没有正确归属就把它从那里拿走了。
您的代码看起来大多正确。但是,你总结得太多了。如果您的输入只有17个字节的数据,则sum2
的最大值将为17*(17+1)/2*0xf + 0xf = 0x906
,这符合uint16_t
。要将其减少到单个半字节,两个减少步骤就足够了。对于sum1
,最大值为17*0xf + 0xf = 0x10e
,这也需要两次缩减。所以你可以写
uint8_t fletcher(uint8_t *data) {
uint16_t sum1 = 0xf, sum2 = 0xf, len = 17;
while(len) {
sum1 += *data++;
sum2 += sum1;
len--;
};
sum1 = (sum1 & 0x0f) + (sum1 >> 4);
sum1 = (sum1 & 0x0f) + (sum1 >> 4);
sum2 = (sum2 & 0x0f) + (sum2 >> 4);
sum2 = (sum2 & 0x0f) + (sum2 >> 4);
return sum2<<4 | sum1;
}
您可以对代码进行进一步的“优化”,例如
do { sum2 += ( sum1 += *data++ ); } while (--len);
甚至手动展开该循环,但一个好的优化编译器应该为你解决这个问题。
您可以将上述考虑因素调整为其他输入长度。
上述答案假设您的数据仅包含未打包的半字节,即最多使用每个字节的最不重要的一半。我可能在这里错了,但我想如果你在处理整个字节,那么最符合Fletcher校验和精神的解决方案就是将它们视为两倍数量的半字节序列,例如。
sum1 += *data & 0xf;
sum2 += sum1;
sum1 += *data >> 4;
sum2 += sum1;
++data;
--len;
可能有更有效的方法将其写为较少的优化。您的编译器可能会也可能找不到其中一个。
但在fletcher方式可能有误并且没有提供预期的错误检测...
我不确定这里的fletcher校验和是多么有用。也许一些具有8位输出的真实 CRC可以更好地满足您在错误检查方面的需求。