// Copyright (c) 2014-2018 Thomas Fussell // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, WRISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE // // @license: http://www.opensource.org/licenses/mit-license.php // @author: see AUTHORS file #include "../../source/detail/numeric_utils.hpp" #include class numeric_test_suite : public test_suite { public: numeric_test_suite() { register_test(test_float_equals_zero); register_test(test_float_equals_large); } void test_float_equals_zero() { // comparing relatively small numbers (2.3e-6) with 0 will be true by default const float comp_val = 2.3e-6; // about the largest difference allowed by default xlnt_assert(0.f != comp_val); // fail because not exactly equal xlnt_assert(xlnt::detail::float_equals(0.0, comp_val)); xlnt_assert(xlnt::detail::float_equals(0.0, -comp_val)); // fail because diff is out of bounds for fuzzy equality xlnt_assert(!xlnt::detail::float_equals(0.0, comp_val + 0.1e-6)); xlnt_assert(!xlnt::detail::float_equals(0.0, -(comp_val + 0.1e-6))); // if the bounds of comparison are too loose, there are two tweakable knobs to tighten the comparison up //========================================================== // #1: reduce the epsilon_scale (default is 20) // This can bring the range down to FLT_EPSILON (scale factor of 1) xlnt_assert(!xlnt::detail::float_equals(0.0, comp_val, 10)); const float closer_comp_val = 1.1e-6; xlnt_assert(xlnt::detail::float_equals(0.0, closer_comp_val, 10)); xlnt_assert(!xlnt::detail::float_equals(0.0, closer_comp_val + 0.1e-6, 10)); xlnt_assert(xlnt::detail::float_equals(0.0, -closer_comp_val, 10)); xlnt_assert(!xlnt::detail::float_equals(0.0, -(closer_comp_val + 0.1e-6), 10)); //========================================================== // #2: specify the epsilon source as a higher precision type (e.g. double) // This makes the epsilon range quite significantly less xlnt_assert(!xlnt::detail::float_equals(0.0, comp_val)); xlnt_assert(!xlnt::detail::float_equals(0.0, closer_comp_val)); const float tiny_comp_val = 4.4e-15; xlnt_assert(xlnt::detail::float_equals(0.0, tiny_comp_val)); xlnt_assert(!xlnt::detail::float_equals(0.0, tiny_comp_val + 0.1e-15)); xlnt_assert(xlnt::detail::float_equals(0.0, -tiny_comp_val)); xlnt_assert(!xlnt::detail::float_equals(0.0, -(tiny_comp_val + 0.1e-15))); //========================================================== // #3: combine #1 & #2 // for the tightest default precision, double with a scale of 1 xlnt_assert(!xlnt::detail::float_equals(0.0, comp_val, 1)); xlnt_assert(!xlnt::detail::float_equals(0.0, closer_comp_val, 1)); xlnt_assert(!xlnt::detail::float_equals(0.0, tiny_comp_val, 1)); const float really_tiny_comp_val = 2.2e-16; // the limit is +/- std::numeric_limits::epsilon() xlnt_assert(xlnt::detail::float_equals(0.0, really_tiny_comp_val, 1)); xlnt_assert(!xlnt::detail::float_equals(0.0, really_tiny_comp_val + 0.1e-16, 1)); xlnt_assert(xlnt::detail::float_equals(0.0, -really_tiny_comp_val, 1)); xlnt_assert(!xlnt::detail::float_equals(0.0, -(really_tiny_comp_val + 0.1e-16), 1)); //========================================================== // in the world of floats, 2.2e-16 is still significantly different to 0.f (smallest representable float is around 1e-38) // if comparisons are known to involve extremely small numbers (such that +/- 2.2e-16 is too large a band), // a type that specialises std::numeric_limits::epsilon may be passed as the first template parameter // the type itself doesn't actually need to have any behaviour as it is only used as the source for epsilon // struct super_precise{}; // namespace std { // template<> numeric_limits { // double epsilon() { // return 1e-30; // } // } // } // float_equals(0.0, 2e-30, 1); // returns true // float_equals(0.0, 2e-30, 1); // returns false } void test_float_equals_large() { const float compare_to = 20e6; // fp math with arguments of different magnitudes is wierd xlnt_assert(compare_to == compare_to + 1); // x == x + 1 ... xlnt_assert(compare_to != compare_to + 10); // x != x + 10 xlnt_assert(compare_to != compare_to - 10); // x != x - 10 // if the same epsilon was used for comparison of large values as the values around one // we'd have all the issues around zero again xlnt_assert(xlnt::detail::float_equals(compare_to, compare_to + 49)); xlnt_assert(!xlnt::detail::float_equals(compare_to, compare_to + 50)); xlnt_assert(xlnt::detail::float_equals(compare_to, compare_to - 49)); xlnt_assert(!xlnt::detail::float_equals(compare_to, compare_to - 50)); // float_equals also scales epsilon up to match the magnitude of its arguments // all the same options are available for increasing/decreasing the precision of the comparison // however the the epsilon source should always be of equal or lesser precision than the arguments when away from zero } }; static numeric_test_suite x;