Хэш 32-битный int в 16-битный int? - PullRequest
16 голосов
/ 17 июня 2010

Какими простыми способами можно хэшировать 32-разрядное целое число (например, IP-адрес, например, Unix time_t и т. Д.) До 16-разрядного целого числа?

Например, hash_32b_to_16b(0x12345678) может вернуть 0xABCD.

Давайте начнем с этого ужасного, но функционального примера решения:

function hash_32b_to_16b(val32b) {
    return val32b % 0xffff;
}

Вопрос конкретно о JavaScript, но не стесняйтесь добавлять любые не зависящие от языка решения, желательно без использования библиотечных функций..

Контекст для этого вопроса - генерация уникальных идентификаторов (например, 64-битный идентификатор может состоять из нескольких 16-битных хэшей различных 32-битных значений).Важно избегать столкновений.

Просто = хорошо.Дурацкий + запутанный = забавный.

Ответы [ 6 ]

5 голосов
/ 17 июня 2010

Я думаю, это лучшее, что вы собираетесь получить.Вы могли бы сжать код до одной строки, но в настоящее время в качестве документации приведены переменные:

function hash_32b_to_16b(val32b) {
    var rightBits = val32b & 0xffff; // Left-most 16 bits
    var leftBits = val32b & 0xffff0000; // Right-most 16 bits

    leftBits = leftBits >>> 16; // Shift the left-most 16 bits to a 16-bit value

    return rightBits ^ leftBits; // XOR the left-most and right-most bits
}

Учитывая параметры задачи, решение best будет иметь каждый 16-битхэш точно соответствует 2 ^ 16 32-битным числам.Это также могло бы по-разному хэшировать 32-битные числа IMO.Если я что-то упустил, я верю, что это решение делает эти две вещи.

Я бы сказал, что безопасность не может быть рассмотрена в этой проблеме, поскольку хешированное значение слишком мало.Я считаю, что решение, которое я дал, обеспечивает равномерное распределение 32-битных чисел по 16-битным хэшам

3 голосов
/ 17 июня 2010

Это зависит от природы целых чисел. Если они могут содержать несколько битовых масок или могут различаться степенью двойки, то простые XOR будут иметь высокую вероятность коллизий. Вы можете попробовать что-то вроде (i>>16) ^ ((i&0xffff) * p), где p - простое число.

Все хеши безопасности, такие как MD5, хороши, но они явно излишни. Все, что сложнее, чем CRC16, излишне.

2 голосов
/ 06 августа 2018

Ключом к максимальному сохранению энтропии некоторого исходного 32-битного «сигнала» является обеспечение того, чтобы каждый из 32 входных битов имел независимую и равную способность изменятьзначение 16-разрядного выходного слова.

Поскольку OP запрашивает размер в битах, равный половине исходного размера, самый простой способ удовлетворить этот критерий - XOR .верхняя и нижняя половины, как уже упоминалось.Использование XOR является оптимальным, поскольку - как и очевидно по определению XOR - независимое переключение любого из 32 входных битов гарантированно изменит значение16-битный вывод.

Проблема становится более интересной, когда вам необходимо дальнейшее уменьшение, помимо половины размера , скажем, от 32-битного входа до,скажем, 2-битный вывод .Помните, цель состоит в том, чтобы сохранить как можно больше энтропии от источника, поэтому решения, которые включают в себя наивное маскирование двух младших битов с (i & 3), как правило, движутся в неправильном направлении;выполнение этого гарантирует , что никакие биты не могут быть обработаны, кроме немаскированных битов, влияющих на результат, и это обычно означает, что есть произвольная, возможно, полезная часть сигнала времени выполнения, которая суммируетсяотбрасывается без принципа.

Исходя из предыдущего параграфа, вы, конечно, могли бы выполнить итерацию с XOR три раза, чтобы получить 2-битный выход с желаемым свойством равного влияния каждым / любым из входных битов.Конечно, это решение по-прежнему оптимально правильно, но включает циклы или несколько развернутых операций, которые, как оказывается, не нужны!

К счастью, есть хорошая техника, состоящая из двух операций , которая дает доказуемо-оптимальный результат для этогоситуация.Как и в случае XOR , он не только гарантирует, что при любом заданном 32-разрядном значении изменение любого одного из входных битов приведет к изменению (например) 2-разрядного выходного значения, но также и в том, чтораспределение 2-битных выходных значений совершенно равномерно.Другими словами, над 4,294,967,296 возможными входными значениями будет точно 1,073,741,824 каждого из четырех возможных 2-битных результатов хеширования { 0, 1, 2, 3 }.

В методе, который я здесь упоминаю, используются определенные магические значениякоторый я обнаружил с помощью исчерпывающего поиска, и который, кажется, не обсуждается в других местах в Интернете, по крайней мере, для конкретного обсуждаемого здесь использования (т. е. обеспечения равномерного распределения хешей, максимально сохраняющего энтропию).Любопытно, что в соответствии с этим же исчерпывающим поиском магические значения на самом деле уникальны. Это означает, что для каждой целевой ширины в битах { 16, 8, 4, 2 } магическое значение, которое я показываю ниже, является только значение, которое при использовании, как я покажу здесь, удовлетворяет идеальным критериям хэширования, описанным выше.

Без дальнейших церемоний уникальная и математически оптимальная процедура для хеширования 32 битов до n = { 16, 8, 4, 2 } равна умножению по магическому значению, соответствующему n (без знака, отбрасывание переполнения), а затем принять n старшие биты результата.Чтобы выделить эти результирующие биты в качестве значения хеш-функции в диапазоне [0 ... (2ⁿ - 1)], просто сдвиньте вправо (без знака!) Результат умножения на 32 - n бит.

"магические" значения и C-подобный синтаксис выражения выглядит следующим образом:

Максимально сохраняющий энтропию хэш для сокращения с 32-битных до ...

Target Bits    Multiplier    Right Shift          Expression
-----------   ------------   -----------   -----------------------
    16         0x80008001        16        (i * 0x80008001) >> 16
     8         0x80808081        24        (i * 0x80808081) >> 24
     4         0x88888889        28        (i * 0x88888889) >> 28
     2         0xAAAAAAAB        30        (i * 0xAAAAAAAB) >> 30


Примечания:

  1. Использовать 32-разрядное умножение без знака и отбрасывать любое переполнение (умножение на 64 бита не требуется).
  2. При выделении результата с использованием сдвига вправо (как показано) обязательно используйте операцию сдвига без знака .


[ edit: добавлена ​​таблица для 64-битных входных значений]

Максимально сохраняющий энтропию хеш для уменьшения 64-битного значения до ...

Target Bits   Multiplier           Right Shift              Expression
-----------   ------------------   -----------   -------------------------------
    32        0x8000000080000001       32        (i * 0x8000000080000001) >> 32
    16        0x8000800080008001       48        (i * 0x8000800080008001) >> 48
     8        0x8080808080808081       56        (i * 0x8080808080808081) >> 56
     4        0x8888888888888889       60        (i * 0x8888888888888889) >> 60
     2        0xAAAAAAAAAAAAAAAB       62        (i * 0xAAAAAAAAAAAAAAAB) >> 62



Дальнейшее обсуждение

Я нашел все это довольно круто. С практической точки зрения ключевое информационное теоретическое требование - это гарантия того, что для любого входного значения m-bit и соответствующего ему результата хеш-значения n-bit переключение любого из m исходных битов всегда вызывает некоторые изменения в n-bit значении результата . Теперь, хотя всего имеется 2ⁿ возможных значений результата, одно из них уже "используется" , поскольку переключение результата на это значение не изменится вообще. Это оставляет только 2ⁿ - 1 результирующие значения, которые могут быть использованы всем набором m входных значений, созданных одним переворотом.

Давайте рассмотрим пример; на самом деле, чтобы показать, как эта техника может граничить с жутким или совершенно волшебным, мы рассмотрим более экстремальный случай, когда m = 64 и n = 2. С 2 выходными битами возможны четыре значения результата, { 0, 1, 2, 3 }. Предполагая произвольное 64-битное входное значение 0x7521d9318fbdf523, мы получаем его 2-битное хеш-значение 1:

 (0x7521d9318fbdf523 * 0xAAAAAAAAAAAAAAAB) >> 62   // result -->  '1'

Но этот результат влечет за собой то, что без значения в наборе из 64 значений , где один бит 0x7521d9318fbdf523 равен переключено может иметь то же значение результата . То есть ни один из этих 64 других результатов не может использовать значение 1, и все должны вместо этого использовать либо 0, 2, либо 3. Когда каждое из 2⁶⁴ входных значений эгоистично захватывает одну четверть выходного пространства для себя из 64 своих пиров, существует ли одновременно удовлетворительное решение для всех?

Достаточно, конечно, чтобы показать, что (точно?) Один делает , вот значения хеш-результата, перечисленные по порядку, для входов, которые переворачивают один бит 0x7521d9318fbdf523 (по одному за раз) ), от MSB (позиция 63) до LSB (0).

3 2 0 3 3 3 3 3 3 0 0 0 3 0 3 3 0 3 3 3 0 0 3 3 3 0 0 3 3 0 3 3 
0 0 3 0 0 3 0 3 0 0 0 3 0 3 3 3 0 3 0 3 3 3 3 3 3 0 0 0 3 0 0 3    // <-- no '1' values

Как видите, не существует значений 1, что означает, что каждый бит в источнике "как есть" должен влиять на результат (или если вы предпочитаете, состояние de facto каждого бита в 0x7521d9318fbdf523 является существенным , чтобы результат не был "не- 1"). Поскольку независимо от того, какое однобитное изменение вы вносите в 64-битный вход, 2-битное значение результата больше не будет 1.

Имейте в виду, что приведенная выше таблица «пропущенных значений» была выведена из анализа только одного случайно выбранного значения примера 0x7521d9318fbdf523; каждое другое возможное входное значение имеет аналогичную собственную таблицу, каждая из которых до жути пропускает фактическое значение результата своего владельца, но все же каким-то образом является глобально непротиворечивой в своем членстве в наборе. Это свойство по существу соответствует максимальному сохранению доступной энтропии во время задачи (с изначально потерями) уменьшения ширины в битах.

Таким образом, мы видим, что каждое из 2⁶⁴ возможных значений источника независимо накладывает, ровно на 64 других значения источника, ограничение исключения одного из возможных значений результата. Что бросает вызов моей интуиции по этому поводу, так это то, что существуют неописанные квадриллионы этих наборов из 64 членов, каждый из которых также принадлежит к 63 прочим , по-видимому, не связанным наборам бит-твидлинга. И все же, несмотря на эту самую запутанную загадку переплетенных ограничений, тем не менее тривиально использовать одно (я полагаю) разрешение, которое одновременно точно удовлетворяет их всем.

Все это, по-видимому, связано с чем-то, что вы, возможно, заметили в таблицах выше: а именно, я не вижу очевидного способа распространить методику на случай сжатия до результата 1-бит .В этом случае есть только два возможных результирующих значения { 0, 1 }, поэтому, если любое / каждое заданное (например) 64-битное входное значение все еще суммарно исключает его собственный результат из всех 64 его соседей с однобитовым переворачиваниемто, что теперь, по существу, налагает другие , только оставшиеся значения для этих 64. Математическая разбивка, которую мы видим в таблице, кажется, сигнализирует, что одновременный результатв таких условиях мост слишком далеко.

Другими словами, специальная «сохраняющая информацию» характеристика из XOR (то есть ее роскошно надежная гарантия того, что в отличие от И , ИЛИ и т. Д., Это c̲a̲n̲ и w̲i̲l̲l̲ всегда немного меняются), что не удивительно, требует определенных затрат, а именно, крайне необоротного спросадля некоторого количества места локтя - по крайней мере 2 бита - для работы.

2 голосов
/ 17 июня 2010

Предполагая, что вы ожидаете, что младшие значащие биты будут «меняться» больше всего, я думаю, что вы, вероятно, получите достаточно хорошее распределение, просто используя младшие 16 битов значения в качестве хэша.

Если числа, которые вы собираетесь хэшировать, не будут иметь такого распределения, тогда может быть полезен дополнительный шаг ксорирования в верхних 16 битах.

Конечно, это предложение, если вымы собираемся использовать хеш просто для какой-то схемы поиска / хранения и не ищем крипто-связанных свойств не-угадывания и необратимости (которые на самом деле и не покупают предложения по хорунгу).

2 голосов
/ 17 июня 2010

Я бы сказал, просто примените стандартный хеш, такой как sha1 или md5, а затем возьмите последние 16 бит этого.

0 голосов
/ 17 июня 2010

Что-то простое, как это ....

function hash_32b_to_16b(val32b) {    
    var h = hmac(secretKey, sha512);
    var v = val32b;
    for(var i = 0; i < 4096; ++i)
        v = h(v);
    return v % 0xffff;
}
...