1

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:

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?

Ben
  • 9,184
  • 1
  • 43
  • 56
  • When I say "generic" I mean, if there were a standard `NaNSafeEqual` then I could specialize it for a `VecND` type, so I could have `VecND::operator==` have the typical NaN behavior but I could provide a specialized `NaNSafeEqual>` that for two bitwise identical vectors of NaNs would evaluate as `true`. – Ben Apr 23 '19 at 17:16

0 Answers0