Мы знаем, что NaN! = NaN для IEEE плавает. В результате ряд «очевидных» операций с плавающими числами в пинтах имеет скрытые ошибки, в которых NaN в данных могут ужасно запутаться. Например:
Очевидный подход примерно такой:
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;
}
};
Это осложняется тем фактом, что std::hash<float>
хэширует битовые значения (по крайней мере, в некоторых реализациях), поэтому для std::unordered_set<float, std::hash<float>, ...>
нам нужна наша KeyEqual
функция для сравнения KeyEqual()(NaN, NaN) == true
, но KeyEqual()(NaN, -NaN) == false
. Для std::unordered_set
мы можем выполнить побитовое сравнение, за исключением того, что нам все еще нужно -0 == 0
.
Я почти уверен, что могу написать надежный (может быть, не быстрый?) NaNSafeLess<T>
и NaNSafeEqual<T>
, но такие вещи, как известно, сложно сделать правильно, и, более того, сложно сделать это универсальным. Я думаю, что это, вероятно, правильно для встроенных типов POD, хотя оно не является полностью универсальным, потому что оно будет static_assert
при sizeof(T) > sizeof(std::size_t)
, и использует union
хитрым способом.
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;
}
};
Итак: существует ли общий стандартный способ записи less
и equal
, который позволяет безопасно хранить значения с плавающей точкой в std :: set и / или std :: unordered_set? Если нет, предоставляет ли Boost или какая-либо другая широко используемая библиотека?