I believe it is important to remember the following. When you implement a hash function to be used in a hash table, you must also provide the equality operator to go together, and they must be in sync.
Suppose you have implemented a hash function like this:
uint32_t hash(float f);
and provided an equality operator too:
bool equal(float f1, float f2);
You need to ensure that they behave like this:
if hash(v1) != hash(v2) then equal(v1, v2) == false and also
if equal(v1, v2) == true then hash(v1) == hash(v2).
As mentioned in other posts, floating point types have special values that mess up the hashing, such as -0, +0, quiet NAN, signaling NAN. These have different binary representation.
If you where to choose a hash function that casts bits of float to an 32 bit int, then you cannot use only regular boolean operator == to implement equal(), because
std::bit_cast<float, uint32_t>(-0) != std::bit_cast<float, uint32_t>(+0), but -0 == +0. At the same time std::bit_cast<float, uint32_t>(QNAN) == std::bit_cast<float, uint32_t>(QNAN), while QNAN == QNAN is false.
Usually we don't want to distinguish +0 and -0, so we need to filter them out before bit_cast, and also we want to account for accidental NAN value getting into out data, then we would have to check if both values are NAN and return true.
Assuming that you have a good hash for uint32_t, say called hash_uint32, the following combination of hash and equals should work well together:
uint32_t hash(float f)
{
float normalized_f = f != 0 ? f : 0.0;
return hash_uint32(std::bit_cast<float, uint32_t>(normalized_f));
}
bool equal(float f1, float f1)
{
return f1 == f2 ||
std::bit_cast<float, uint32_t>(f1) == std::bit_cast<float, uint32_t>(f2);
}
These ensure that -0 and +0 are treated as same number and also that strange NAN values would not break your hash table forever.