我有几台设备。它们中的每一个都有其由11个字节组成的唯一ID,如:0x34 0x57 0x36 0x31 0x38 0x39 0x15 0x0e 0x00 0x0f 0x00。我希望生成唯一编号(0-32767或0-65535)以在广播FindDevices命令时延迟应答。当设备在同一时刻开始应答时,存在RS485总线争用问题,所以我想避免这种情况。
我的第一次尝试是将所有11个字节的唯一ID,调用srand(sum)与种子生成器相加,然后调用rand()来获取我的值。 但不幸的是,它的解决方案很糟糕,在我的一批设备中,我有2台设备具有唯一的ID,但是相同的"(不是那么)独特的"号码:(
UID1:34 57 36 31 38 39 15 0e 00 0f 00 总和:405十进制 生成的数字:23860
UID2:34 57 33 31 35 39 04 18 00 1c 00 总和:405十进制 生成的数字:23860
设备不知道在其他设备中生成了什么号码或者他们拥有哪些唯一ID,因此他们无法简单地比较它们。
知道如何生成这样的唯一编号(0-32767或0-65535)?
修改
我工作台批次的唯一ID(以十六进制)列表:
01. 34 57 36 31 38 39 15 0E 00 02 00
02. 34 57 36 31 38 39 15 0E 00 06 00
03. 34 57 36 31 38 39 15 0E 00 0A 00
04. 34 57 36 31 38 39 15 0E 00 0E 00
05. 34 57 36 31 38 39 15 0A 00 14 00
06. 34 57 36 31 38 39 15 0A 00 1C 00
07. 34 57 36 31 38 39 15 09 00 23 00
08. 34 57 36 31 38 39 15 0A 00 24 00
09. 34 57 36 31 38 39 0E 1D 00 1A 00
10. 34 57 36 31 38 39 15 0E 00 09 00
11. 34 57 33 31 35 39 04 10 00 20 00
12. 34 57 33 31 35 39 04 18 00 1C 00
13. 34 57 36 31 38 39 15 0E 00 0F 00
14. 34 57 36 31 38 39 15 0E 00 13 00
15. 34 57 36 31 38 39 15 0E 00 17 00
16. 34 57 36 31 38 39 15 0E 00 1F 00
17. 34 57 36 31 38 39 15 0A 00 25 00
看起来它们是唯一的但很多字节都是重复/不变的。即使输入值彼此接近,良好的解决方案也应该生成放在整个范围内的值:)
EDIT2: 以下是您的答案中所有解决方案的结果:
Test results:
OP: 1977, H1L: 14759, H1H: 13938, H2L: 7189, H2H: 36686, H3L: 14759, H3H: 13938, H4: 2652, PRS: 61086
OP: 3669, H1L: 13735, H1H: 12914, H2L: 8213, H2H: 37710, H3L: 13735, H3H: 12914, H4: 6748, PRS: 25852
OP: 5361, H1L: 16807, H1H: 15986, H2L: 5141, H2H: 34638, H3L: 16807, H3H: 15986, H4: 10844, PRS: 40974
OP: 7053, H1L: 15783, H1H: 14962, H2L: 6165, H2H: 35662, H3L: 15783, H3H: 14962, H4: 14940, PRS: 19836
OP: 7899, H1L: 18507, H1H: 25943, H2L: 3441, H2H: 24681, H3L: 18507, H3H: 25943, H4: 21076, PRS: 4898
OP: 11283, H1L: 20555, H1H: 27991, H2L: 1393, H2H: 22633, H3L: 20555, H3H: 27991, H4: 29268, PRS: 10065
OP: 13821, H1L: 391, H1H: 26260, H2L: 21557, H2H: 24364, H3L: 391, H3H: 26260, H4: 36434, PRS: 63904
OP: 14667, H1L: 30795, H1H: 38231, H2L: 23902, H2H: 12393, H3L: 30795, H3H: 38231, H4: 37460, PRS: 46300
OP: 15513, H1L: 23009, H1H: 40628, H2L: 31688, H2H: 9996, H3L: 23009, H3H: 40628, H4: 27066, PRS: 21678
OP: 21322, H1L: 17063, H1H: 16242, H2L: 4885, H2H: 34382, H3L: 17063, H3H: 16242, H4: 9820, PRS: 60787
OP: 22168, H1L: 31736, H1H: 54522, H2L: 22961, H2H: 61623, H3L: 31736, H3H: 54522, H4: 32801, PRS: 20737
OP: 23860, H1L: 3760, H1H: 10032, H2L: 18188, H2H: 40592, H3L: 3760, H3H: 10032, H4: 28721, PRS: 50696
OP: 23860, H1L: 15527, H1H: 14706, H2L: 6421, H2H: 35918, H3L: 15527, H3H: 14706, H4: 15964, PRS: 28319
OP: 25552, H1L: 10407, H1H: 9586, H2L: 11541, H2H: 41038, H3L: 10407, H3H: 9586, H4: 20060, PRS: 60097
OP: 27244, H1L: 9383, H1H: 8562, H2L: 12565, H2H: 42062, H3L: 9383, H3H: 8562, H4: 24156, PRS: 5512
OP: 30628, H1L: 11431, H1H: 10610, H2L: 10517, H2H: 40014, H3L: 11431, H3H: 10610, H4: 32348, PRS: 55107
OP: 31474, H1L: 30539, H1H: 37975, H2L: 24158, H2H: 12649, H3L: 30539, H3H: 37975, H4: 38484, PRS: 4379
OP: 20035, H1L: 0, H1H: 0, H2L: 21948, H2H: 50624, H3L: 0, H3H: 0, H4: 0, PRS: 26124
OP 是我原来的方法,它很差,并为列出的UID生成非唯一数字。 H1L 和 H1H 是chux提供的简化解决方案。 H2L,H2H,H3L,H3H 是我的修改(添加〜下部或上部)以查看它是否产生更好的结果。 H4 是alain提出的解决方案。 PRS 是Pearson返回由mattinbits建议的uint16_t。
获胜者是...... PRS! :)所有建议都会从列出的UID生成唯一的数字,因此它们是正确的,但 Pearson哈希提供了最佳的结果差异(在Excel中检查;))。 感谢您的帮助!
答案 0 :(得分:4)
要从大数字(11字节或88位)生成15或16位范围内的唯一编号是hash function并且容易发生碰撞,如OP&#39所发生的那样; s生成的数字23860。
所有11个字节的总和非常弱,因为11个字节的总和在0到11 * 255或2805的范围内,具有非常不均匀的分布。因此代码仅为srand()
生成2806个不同的种子。使用比8位更宽的整数分组本来会更好。建议64位组彼此独占或相互配合。
使用srand() / rand()
是一种方法,但具有弱点:1)可移植性:不同平台上的相同数据可能产生不同的数字,因为C对rand()
方法的描述很少。 2)因为它需要共享全局状态变量的2个函数,所以代码必须确保另一个线程/中断不会搞砸或者这些调用使用rand()
来破坏其他函数的一致性。 rand()
的一个大问题是在RAND_MAX
为32767,其最小指定值且代码正在尝试[0 ... 65536]的系统上。
我发现一致性很重要,因为它能够在多个平台上使用相同的测试代码:代码维护的一个显着优势。
@mattinbits建议使用一个好的8/16位解决方案。为什么重新发明轮子?然而,我没有驾驶战车轮胎,所以...
如果OP似乎不需要整个范围[0 ... 32767]或[0 ... 65536]中的标识符(OP是否意味着65535?),请考虑简单的便携式可重复哈希方法,在%
附近的prime接近限制,以便很好地混合比特。
// return numbers 0 ... 32748 or 0 ... 65536
unsigned long Hash(const unsigned char ID[11]) {
unsigned long long Upper;
unsigned long Lower;
Upper = (ID[0]*1ULL<<56) | (ID[1]*1ULL<<48) | (ID[2]*1ULL<<40) | (ID[3]*1ULL<<32) |
(ID[4]*1UL<<24) | (ID[5]*1UL<<16) | (ID[6]*1U<<8) | ID[7];
Lower = (ID[8]*1UL<<16) | (ID[9]*1U<<8) | ID[10];
// Greatest prime <= 32768
#define Prime_LE_32768 32749
return (Upper ^ Lower) % Prime_LE_32768;
// or
// Greatest prime <= 65537
#define Prime_LE_65537 65537
return (Upper ^ Lower) % Prime_LE_65537;
}
[编辑]可能的简化。
unsigned Hash(const uint8_t ID[11], unsigned prime) {
uint64_t H[2] = {0};
memcpy(H, ID, 11);
return (H[0] ^ H[1]) % prime;
}
const unsigned char ID[][11] = {
{ 0x34, 0x57, 0x36, 0x31, 0x38, 0x39, 0x15, 0x0E, 0x00, 0x02, 0x00 },
{ 0x34, 0x57, 0x36, 0x31, 0x38, 0x39, 0x15, 0x0E, 0x00, 0x06, 0x00 },
{ 0x34, 0x57, 0x36, 0x31, 0x38, 0x39, 0x15, 0x0E, 0x00, 0x0A, 0x00 },
{ 0x34, 0x57, 0x36, 0x31, 0x38, 0x39, 0x15, 0x0E, 0x00, 0x0E, 0x00 },
{ 0x34, 0x57, 0x36, 0x31, 0x38, 0x39, 0x15, 0x0A, 0x00, 0x14, 0x00 },
{ 0x34, 0x57, 0x36, 0x31, 0x38, 0x39, 0x15, 0x0A, 0x00, 0x1C, 0x00 },
{ 0x34, 0x57, 0x36, 0x31, 0x38, 0x39, 0x15, 0x09, 0x00, 0x23, 0x00 },
{ 0x34, 0x57, 0x36, 0x31, 0x38, 0x39, 0x15, 0x0A, 0x00, 0x24, 0x00 },
{ 0x34, 0x57, 0x36, 0x31, 0x38, 0x39, 0x0E, 0x1D, 0x00, 0x1A, 0x00 },
{ 0x34, 0x57, 0x36, 0x31, 0x38, 0x39, 0x15, 0x0E, 0x00, 0x09, 0x00 },
{ 0x34, 0x57, 0x33, 0x31, 0x35, 0x39, 0x04, 0x10, 0x00, 0x20, 0x00 },
{ 0x34, 0x57, 0x33, 0x31, 0x35, 0x39, 0x04, 0x18, 0x00, 0x1C, 0x00 },
{ 0x34, 0x57, 0x36, 0x31, 0x38, 0x39, 0x15, 0x0E, 0x00, 0x0F, 0x00 },
{ 0x34, 0x57, 0x36, 0x31, 0x38, 0x39, 0x15, 0x0E, 0x00, 0x13, 0x00 },
{ 0x34, 0x57, 0x36, 0x31, 0x38, 0x39, 0x15, 0x0E, 0x00, 0x17, 0x00 },
{ 0x34, 0x57, 0x36, 0x31, 0x38, 0x39, 0x15, 0x0E, 0x00, 0x1F, 0x00 },
{ 0x34, 0x57, 0x36, 0x31, 0x38, 0x39, 0x15, 0x0A, 0x00, 0x25, 0x00 },
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } };
#define Prime_LE_32768 32749
#define Prime_LE_65536 65521u
void test() {
int i, j;
for (i = 0; i < sizeof ID / sizeof ID[0]; i++) {
const char *comma = "";
for (j = 0; j < 11; j++) {
printf("%s%02X", comma, ID[i][j]);
comma = "-";
}
printf(" %5u", Hash(ID[i], Prime_LE_32768));
printf(" %5u\n", Hash(ID[i], Prime_LE_65536));
}
puts("");
}
输出
34-57-36-31-38-39-15-0E-00-02-00 14759 13938
34-57-36-31-38-39-15-0E-00-06-00 13735 12914
34-57-36-31-38-39-15-0E-00-0A-00 16807 15986
34-57-36-31-38-39-15-0E-00-0E-00 15783 14962
34-57-36-31-38-39-15-0A-00-14-00 18507 25943
34-57-36-31-38-39-15-0A-00-1C-00 20555 27991
34-57-36-31-38-39-15-09-00-23-00 391 26260
34-57-36-31-38-39-15-0A-00-24-00 30795 38231
34-57-36-31-38-39-0E-1D-00-1A-00 23009 40628
34-57-36-31-38-39-15-0E-00-09-00 17063 16242
34-57-33-31-35-39-04-10-00-20-00 31736 54522
34-57-33-31-35-39-04-18-00-1C-00 3760 10032
34-57-36-31-38-39-15-0E-00-0F-00 15527 14706
34-57-36-31-38-39-15-0E-00-13-00 10407 9586
34-57-36-31-38-39-15-0E-00-17-00 9383 8562
34-57-36-31-38-39-15-0E-00-1F-00 11431 10610
34-57-36-31-38-39-15-0A-00-25-00 30539 37975
00-00-00-00-00-00-00-00-00-00-00 0 0
答案 1 :(得分:3)
您要做的是有效地为您的ID找到哈希输出小于输入的哈希值,因此始终存在冲突的风险,即两个设备ID产生相同的短ID。尽管如此,使用适当的散列函数比使用求和方法更好。只需寻找一个简单的8位或16位散列函数并使用它。例如。 https://en.m.wikipedia.org/wiki/Pearson_hashing
答案 2 :(得分:1)
每个设备自己生成ID的解决方案,如“哈希”方法,具有无需通信即可工作的优势。另一方面,数量必须足够大,以便碰撞不太可能。 16位并不多。
我看到的一种实用方法是使用基于设备属性的数字,如处理器序列号或RS485总线地址(但我怀疑你想要使用我们正在谈论的ID这个目的)。
如果11字节的唯一ID或您可以访问的序列号不是随机的,则可以使用该非随机性来制作比任何通用的更好的散列函数。
这是因为通用散列函数必须均匀地混合位,因为它不知道哪些位比其他位变化更多。如果您知道哪些位变化最大,则可以使用此知识来构建更好的散列函数。例如,如果您有一个计数器,则最低有效位是为“唯一”ID选择的位。
如果无法做到这一点,我会尝试制作一个检测碰撞ID的通信方案。这应该是可能的,因为每个设备都可以看到RS485总线上的所有通信。
编辑:根据示例数据,我会选择这些位:
00 00 00 00 01 00 1B 1F 00 3F 00
并实现一个哈希函数,如:
unsigned short hash(const unsigned char ID[11]) {
return (ID[9] << 10)
| ((ID[6] & 0x18) << 5)
| ((ID[6] & 0x03) << 6)
| ((ID[7] & 0x1F) << 1)
| (ID[4] & 0x01);
}
此功能很可能为将来添加的设备生成唯一标识符。