We know that NaN != NaN for IEEE floats. As a result, a number of "obvious" operations on floating pint numbers have a hidden gotchas in which NaNs in the data can mess things up terribly. For example:
- Are IEEE floats valid key types for std::map and std::set?
- Inserting multiple not-a-numbers into a std::unordered_set<double>
The obvious approach is something like this:
template <typename T>
struct NaNSafeEqual0 {
bool operator()(const T& a, const T& b) const {
if (!(a == a) || !(b == b)) {
return (a == a) == (b == b); // <- Still has a gotcha: see below.
}
return a == b; // This deals with 0.0 == -0.0;
}
};
This is complicated by the fact that std::hash<float>
hashes bitwise values (in some implementations, at least), so for std::unordered_set<float, std::hash<float>, ...>
we need our KeyEqual
function to compare KeyEqual()(NaN, NaN) == true
but KeyEqual()(NaN, -NaN) == false
. For std::unordered_set
, we can do bitwise comparison, except we still need -0 == 0
.
I'm pretty sure I can write a robust (maybe not fast?) NaNSafeLess<T>
and NaNSafeEqual<T>
, but this sort of thing is notoriously tricky to get exactly right, and furthermore, it's tricky to make it generic. I think this is probably correct for the built-in POD types, although it's not fully generic 'cause it'll static_assert
when sizeof(T) > sizeof(std::size_t)
, and uses union
in a dicy way.
template <typename T>
struct NaNSafeEqual {
std::size_t operator()(const T& a, const T& b) const {
if (a == a || b == b) {
return a == b; // At least one isn't nan.
}
static_assert(sizeof(T) <= sizeof(std::size_t));
union {
T data;
std::size_t s;
} ua, ub;
ua.s = 0;
ub.s = 0;
ua.data = a;
ub.data = b;
return ua.s == ub.s;
}
};
So: is there a generic standard way to write less
and equal
that allows safe storage of floats in a std::set and/or a std::unordered_set? If not, does Boost or some other widely-used library provide one?