Хеширование значений плавающей запятой

Недавно мне было любопытно, как работали алгоритмы хеширования для плавающих точек, поэтому я посмотрел исходный код для boost::hash_value. Оказывается довольно сложно. Фактическая реализация пеет по каждой цифре в радиусе и накапливает хеш-значение. По сравнению с целыми хеш-функциями, это гораздо более активное участие.

Мой вопрос: почему алгоритм хеширования с плавающей запятой будет более сложным? Почему не просто хэш-представление двоичного представления значения с плавающей запятой, как если бы оно было целым?

Как

std::size_t hash_value(float f)
{
  return hash_value(*(reinterpret_cast<int*>(&f)));
}

Я понимаю, что float не гарантированно будет такого же размера, как int для всех систем, но такого рода вещи можно было бы обработать несколькими мета-программами шаблонов, чтобы вывести интегральный тип с одинаковым размером как float. Итак, каково преимущество введения совершенно другой хеш-функции, которая специфически работает с типами с плавающей запятой?

Ответы

Ответ 1

Почему вы хотите хеш-значения с плавающей запятой? По той же причине, что сравнение значений с плавающей запятой для равенства имеет ряд подводных камней, хеширование их может иметь сходные (отрицательные) последствия.

Однако, учитывая, что вы действительно этого хотите, я подозреваю, что алгоритм ускорения сложный, потому что, когда вы учитываете денормализованные числа, разные битовые шаблоны могут представлять одинаковое число (и, вероятно, должны иметь одинаковый хеш). В IEEE 754 также есть как положительные, так и отрицательные значения 0, которые сравнивают одинаковые, но имеют разные битовые шаблоны.

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

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

Ответ 2

Взгляните на https://svn.boost.org/trac/boost/ticket/4038

В сущности это сводится к двум вещам:

  • Переносимость: когда вы берете двоичное представление float, то на некоторой платформе возможно, что float с одним значением имеет несколько представлений в двоичном формате. Я не знаю, есть ли на самом деле платформа, где такая проблема существует, но с усложнением денормированных чисел, я не уверен, действительно ли это может произойти.

  • вторая проблема - это то, что вы предложили, возможно, что sizeof(float) не равно sizeof(int).

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

Ответ 3

Одна из причин не просто использовать бит-шаблон состоит в том, что некоторые разные битовые шаблоны должны считаться равными и, следовательно, иметь один и тот же хэш-код, а именно

  • положительный и отрицательный нуль
  • возможно денормализованные числа (я не думаю, что это может произойти с IEEE 754, но C разрешает другие представления с плавающей точкой).
  • возможно, NAN (их много, по крайней мере, в IEEE 754. На самом деле это требует, чтобы шаблоны NAN считались неравными с самим собой, что, возможно, не может быть значимо использовано в хэш-таблице)

Ответ 4

Я предполагаю, что две машины с несовместимыми хеш-форматами с плавающей запятой имеют одинаковое значение.