Я обнаружил, что следующий алгоритм обеспечивает очень хорошее статистическое распределение. Каждый входной бит влияет на каждый выходной бит с вероятностью около 50%. Коллизий нет (каждый вход приводит к другому выходу). Алгоритм быстрый, за исключением случаев, когда в CPU нет встроенной единицы умножения целых чисел. Код C, предполагая, что int
является 32-битным (для Java замените >>
на >>>
и удалите unsigned
):
unsigned int hash(unsigned int x) {
x = ((x >> 16) ^ x) * 0x45d9f3b;
x = ((x >> 16) ^ x) * 0x45d9f3b;
x = (x >> 16) ^ x;
return x;
}
Магическое число было рассчитано с помощью специальной многопоточной тестовой программы , которая работала в течение многих часов и рассчитывала лавинный эффект (количество выходных битов, которые изменяются при изменении одного входного бита; должно быть около 16 в среднем), независимость изменений выходного бита (выходные биты не должны зависеть друг от друга) и вероятность изменения каждого выходного бита в случае изменения любого входного бита. Рассчитанные значения лучше, чем у 32-разрядного финализатора, используемого MurmurHash , и почти столь же хороши (не совсем), как при использовании AES . Небольшое преимущество заключается в том, что одна и та же константа используется дважды (она сделала ее немного быстрее в последний раз, когда я тестировал, не уверен, что это все еще так).
Вы можете полностью изменить процесс (получить входное значение из хэша), если заменить 0x45d9f3b
на 0x119de1f3
( мультипликативный обратный ):
unsigned int unhash(unsigned int x) {
x = ((x >> 16) ^ x) * 0x119de1f3;
x = ((x >> 16) ^ x) * 0x119de1f3;
x = (x >> 16) ^ x;
return x;
}
Для 64-битных чисел я предлагаю использовать следующее, даже если оно будет не самым быстрым. Этот основан на splitmix64 , который, кажется, основан на статье блога Better Bit Mixing (микс 13).
uint64_t hash(uint64_t x) {
x = (x ^ (x >> 30)) * UINT64_C(0xbf58476d1ce4e5b9);
x = (x ^ (x >> 27)) * UINT64_C(0x94d049bb133111eb);
x = x ^ (x >> 31);
return x;
}
Для Java используйте long
, добавьте L
к константе, замените >>
на >>>
и удалите unsigned
. В этом случае реверс более сложен:
uint64_t unhash(uint64_t x) {
x = (x ^ (x >> 31) ^ (x >> 62)) * UINT64_C(0x319642b2d24d8ec3);
x = (x ^ (x >> 27) ^ (x >> 54)) * UINT64_C(0x96de1b173f119089);
x = x ^ (x >> 30) ^ (x >> 60);
return x;
}
Обновление: Вы также можете посмотреть на проект Hash Function Prospector , где перечислены другие (возможно, лучшие) константы.