diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 74c24352..7c6ee18e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -15,6 +15,7 @@ if(STATIC_CRT) endif() file(GLOB CELL_TESTS ${CMAKE_CURRENT_SOURCE_DIR}/cell/*.cpp) +file(GLOB DETAIL_TESTS ${CMAKE_CURRENT_SOURCE_DIR}/detail/*.cpp) file(GLOB PACKAGING_TESTS ${CMAKE_CURRENT_SOURCE_DIR}/packaging/*.cpp) file(GLOB STYLES_TESTS ${CMAKE_CURRENT_SOURCE_DIR}/styles/*.cpp) file(GLOB UTILS_TESTS ${CMAKE_CURRENT_SOURCE_DIR}/utils/*.cpp) @@ -23,6 +24,7 @@ file(GLOB WORKSHEET_TESTS ${CMAKE_CURRENT_SOURCE_DIR}/worksheet/*.cpp) set(TESTS ${CELL_TESTS} + ${DETAIL_TESTS} ${PACKAGING_TESTS} ${STYLES_TESTS} ${UTILS_TESTS} @@ -59,6 +61,7 @@ endif() source_group(helpers FILES ${HELPERS}) source_group(runner FILES ${RUNNER}) source_group(tests\\cell FILES ${CELL_TESTS}) +source_group(tests\\detail FILES ${DETAIL_TESTS}) source_group(tests\\packaging FILES ${PACKAGING_TESTS}) source_group(tests\\serialization FILES ${SERIALIZATION_TESTS}) source_group(tests\\styles FILES ${STYLES_TESTS}) diff --git a/tests/detail/numeric_util_test_suite.cpp b/tests/detail/numeric_util_test_suite.cpp new file mode 100644 index 00000000..2e9489fb --- /dev/null +++ b/tests/detail/numeric_util_test_suite.cpp @@ -0,0 +1,112 @@ +// 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; \ No newline at end of file