diff --git a/float.cpp b/float.cpp index 80070ca9..5bc723fc 100644 --- a/float.cpp +++ b/float.cpp @@ -101,7 +101,7 @@ ieee_float::almost_equal (floating_t a, } -// Based on the cynus `AlmostEqualUlps` +// Based on the cynus `AlmostEqual2sComplement` template bool ieee_float::almost_equal (floating_t _a, @@ -115,29 +115,39 @@ ieee_float::almost_equal (floating_t _a, union { floating_t f; sint_t s; + uint_t u; } a, b; a.f = _a; b.f = _b; + // Special case the NaNs early so simplify diffs if (std::isnan (a.f) || std::isnan (b.f)) return false; + // Early out, as identity comparisons are reasonably common if (a.s == b.s) return true; - // Order as twos complement to avoid negative zero/zero comparison issues - if (a.s < 0) - a.s = (1L << (TOTAL_BITS - 1)) - a.s; - if (b.s < 0) - b.s = (1L << (TOTAL_BITS - 1)) - b.s; + // Re-base negative floats to be continuous against +ve/-ve 0 + static const union { + floating_t f; + sint_t s; + } NEG_ZERO { -floating_t {0} }; - uint_t diff = std::abs (a.s - b.s); + if (a.s < 0) + a.s = NEG_ZERO.s - a.s; + if (b.s < 0) + b.s = NEG_ZERO.s - b.s; + + // Calculate ULP difference, but don't use abs(a.s - b.s) as it may cause + // signed overflow + uint_t diff = a.u > b.u ? a.u - b.u : b.u - a.u; return diff <= ulps; } - +//----------------------------------------------------------------------------- template class ieee_float< 5, 10>; // ieee_half template class ieee_float< 8, 23>; // ieee_single; template class ieee_float<11, 52>; // ieee_double; diff --git a/test/maths.cpp b/test/maths.cpp index 68c8607c..d3c8d76c 100644 --- a/test/maths.cpp +++ b/test/maths.cpp @@ -8,28 +8,57 @@ using std::sqrt; using std::numeric_limits; -int -main (int, char **) { - std::cerr.precision (15); - std::cout.precision (15); - CHECK (!almost_equal (-2.0, 0.0)); - CHECK (!almost_equal (-2.f, 0.f)); - CHECK ( almost_equal ( 0.0, 0.0)); - //CHECK_HARD ( almost_equal ( 0.0, numeric_limits::min ())); - CHECK ( almost_equal (numeric_limits::infinity (), - numeric_limits::infinity ())); - CHECK (!almost_equal (numeric_limits::infinity (), 0.0)); - CHECK (!almost_equal (numeric_limits::quiet_NaN (), - numeric_limits::quiet_NaN ())); +void +test_comparisons (void) +{ + // Check pos/neg zeroes + CHECK (almost_equal ( 0.f, 0.f)); + CHECK (almost_equal ( 0.f, -0.f)); + CHECK (almost_equal (-0.f, 0.f)); + CHECK (almost_equal (-0.f, -0.f)); - CHECK (almost_equal (0.f, -0.f)); - CHECK (almost_equal (0., -0.)); + CHECK (almost_equal ( 0., 0.)); + CHECK (almost_equal ( 0., -0.)); + CHECK (almost_equal (-0., 0.)); + CHECK (almost_equal (-0., -0.)); + // Check zero comparison with values near the expected cutoff CHECK ( almost_zero (1e-45f)); CHECK (!almost_zero (1e-40f)); CHECK (!exactly_zero (1e-45f)); + // Compare values a little away from zero + CHECK (!almost_equal (-2.0, 0.0)); + CHECK (!almost_equal (-2.f, 0.f)); + + // Compare values at the maximum extreme + CHECK (!almost_equal (-std::numeric_limits::max (), 0.f)); + CHECK (!almost_equal (-std::numeric_limits::max (), + std::numeric_limits::max ())); + + // Compare infinity values + CHECK ( almost_equal (numeric_limits::infinity (), + numeric_limits::infinity ())); + CHECK (!almost_equal (numeric_limits::infinity (), 0.0)); + + // Compare NaNs + CHECK (!almost_equal (0., numeric_limits::quiet_NaN ())); + CHECK (!almost_equal (numeric_limits::quiet_NaN (), 0.)); + + CHECK (!almost_equal (numeric_limits::quiet_NaN (), + numeric_limits::quiet_NaN ())); +} + + +int +main (int, char **) { + // Max out the precision in case we trigger debug output + std::cerr.precision (std::numeric_limits::digits10); + std::cout.precision (std::numeric_limits::digits10); + + test_comparisons (); + CHECK_EQ (min (-2, 0, 2), -2); CHECK_EQ (max (-2, 0, 2), 2);