The article you cited (in rev 1832) looked familiar. For reference, here is what we use:
namespace RelativeFltCmp
{
template <size_t bytes>
struct TypeWithSize
{
typedef void UInt;
};
template <>
struct TypeWithSize<4>
{
typedef uint32_t UInt;
};
template <>
struct TypeWithSize<8>
{
typedef uint64_t UInt;
};
template <typename RawType>
class FloatingPoint
{
public:
typedef typename TypeWithSize<sizeof(RawType)>::UInt Bits;
static const size_t kBitCount = 8*sizeof(RawType);
static const size_t kFracBitCount = std::numeric_limits<RawType>::digits - 1;
static const size_t kExpBitCount = kBitCount - 1 - kFracBitCount;
static const Bits kSignBitMask = static_cast<Bits>(1) << (kBitCount - 1);
static const Bits kFracBitMask = ~static_cast<Bits>(0) >> (kExpBitCount + 1);
static const Bits kExpBitMask = ~(kSignBitMask | kFracBitMask);
static const size_t kMaxUlps = 4;
explicit FloatingPoint(
const RawType& x )
: value_(x)
{}
inline bool AlmostEquals(
const FloatingPoint& rhs ) const
{
// check for NaN to match == behavior
if (is_nan() || rhs.is_nan())
return false;
return ULP_diff(bits_, rhs.bits_) <= kMaxUlps;
}
private:
inline bool is_nan() const
{
return ((kExpBitMask & bits_) == kExpBitMask) &&
((kFracBitMask & bits_) != 0);
}
inline Bits SignAndMagnitudeToBiased(
const Bits& sam ) const
{
if (kSignBitMask & sam)
return ~sam + 1; // two's complement
else
return kSignBitMask | sam; // * 2
}
inline Bits ULP_diff(
const Bits& sam1,
const Bits& sam2 ) const
{
const Bits biased1 = SignAndMagnitudeToBiased(sam1);
const Bits biased2 = SignAndMagnitudeToBiased(sam2);
return (biased1 >= biased2) ? (biased1 - biased2) : (biased2 - biased1);
}
union
{
RawType value_;
Bits bits_;
};
};
}
static inline bool FltCmp(
float lhs,
float rhs )
{
return RelativeFltCmp::FloatingPoint<float>(lhs).AlmostEquals(RelativeFltCmp::FloatingPoint<float>(rhs));
}
static inline bool FltCmp(
double lhs,
double rhs )
{
return RelativeFltCmp::FloatingPoint<double>(lhs).AlmostEquals(RelativeFltCmp::FloatingPoint<double>(rhs));
}