Like those who have come before me, I foolishly have sought to implement a generic, efficient, idiomatic, and most importantly correct method of comparing floating point numbers for equality. Like those who have come before me, I have most likely failed in at least one of those regards. I'd appreciate comments on any aspect of the following code
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <limits>
#include <type_traits>
template<bool B, typename T, typename DEFAULT = void>
struct cond
{
static constexpr bool value = B;
using default_type = DEFAULT;
using type = T;
};
template<typename Head, typename... Tail>
struct select
{
using type =
typename std::conditional<Head::value,
typename Head::type,
typename select<Tail...>::type>::type;
};
template <typename T>
struct select<T>
{
using type = T;
};
template <bool B, typename T, typename DEFAULT>
struct select<cond<B, T, DEFAULT>>
{
using type = typename std::conditional<B, T, DEFAULT>::type;
};
template <typename Head, typename... Tail>
using select_t = typename select<Head, Tail...>::type;
template <typename FloatingPoint>
struct SelectIntegralOfSameSize {
using type = select_t<
cond<sizeof(FloatingPoint) == sizeof(int64_t), int64_t>,
cond<sizeof(FloatingPoint) == sizeof(int32_t), int32_t>,
cond<sizeof(FloatingPoint) == sizeof(int16_t), int16_t>,
cond<sizeof(FloatingPoint) == sizeof(int8_t), int8_t>
>;
static_assert(!std::is_void<type>::value, "Could not find sane integral type");
};
template <typename FP, typename I = typename SelectIntegralOfSameSize<FP>::type>
union Fp_Int_Union {
FP fp_;
I i_;
};
template <typename FP>
bool nearlyEqual(FP left, FP right, FP maxDiff, std::size_t maxUlpsDiff) {
// handle NaNs and infinities
if (!std::isfinite(left) || !std::isfinite(right)) {
return false;
}
// ULP comparison breaks for different signs
if ((left < 0) != (right < 0)) {
// Handle -0 == +0
return left == right;
}
// ULP comparison fails near zero
if (std::abs(left - right) <= maxDiff) {
return true;
}
Fp_Int_Union<FP> l, r;
l.fp_ = left;
r.fp_ = right;
return std::abs(l.i_ - r.i_) <= maxUlpsDiff;
}
<
operator?"? \$\endgroup\$==
operator? And I'd say that given that most real numbers cannot be exactly represented as afloat
ordouble
we usually don't care about exact equality, but approximate equality. \$\endgroup\$