Хеширование значений плавающей запятой
Недавно мне было любопытно, как работали алгоритмы хеширования для плавающих точек, поэтому я посмотрел исходный код для 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
Я предполагаю, что две машины с несовместимыми хеш-форматами с плавающей запятой имеют одинаковое значение.