From d907f4eaff9d8cd1095d171b13ac34fde58fcaba Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 18 Apr 2024 17:07:01 -0400 Subject: [PATCH] xo-ratio: + unit test --- utest/CMakeLists.txt | 57 +++++++++ utest/ratio.test.cpp | 235 +++++++++++++++++++++++++++++++++++++ utest/ratio_utest_main.cpp | 6 + 3 files changed, 298 insertions(+) create mode 100644 utest/CMakeLists.txt create mode 100644 utest/ratio.test.cpp create mode 100644 utest/ratio_utest_main.cpp diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt new file mode 100644 index 00000000..2da04264 --- /dev/null +++ b/utest/CMakeLists.txt @@ -0,0 +1,57 @@ +# xo-ratio/utest/CMakeLists.txt + +set(SELF_EXE utest.ratio) +set(SELF_SRCS + ratio_utest_main.cpp + ratio.test.cpp) + +add_executable(${SELF_EXE} ${SELF_SRCS}) +xo_include_options2(${SELF_EXE}) +add_test(NAME ${SELF_EXE} COMMAND ${SELF_EXE}) + +# ---------------------------------------------------------------- +# in coverage build, target to build+install coverage report + +if (XO_SUBMODULE_BUILD) + # in submodule build, generate aggregate coverage report + # for all xo libraries +else() + set(CCOV_OUTPUT_DIR ${PROJECT_BINARY_DIR}/ccov/html) + set(CCOV_INDEX_FILE ${CCOV_OUTPUT_DIR}/index.html) + set(CCOV_REPORT_EXE ${PROJECT_BINARY_DIR}/gen-ccov) + # CMAKE_INSTALL_DOCDIR + # =default=> DATAROOTDIR/doc/PROJECT_NAME + # =default=> CMAKE_INSTALL_PREFIX/share/doc/xo_flatstring + set(CCOV_INSTALL_DOCDIR ${CMAKE_INSTALL_DOCDIR}/ccov) + + # 'test' target should always be out-of-date + # + # DEPENDS: reminder - can't put 'test' here, requires 'all' target + # + add_custom_command( + OUTPUT ${CCOV_INDEX_FILE} + DEPENDS ${SELF_EXE} + COMMAND ${CCOV_REPORT_EXE} + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + COMMENT "Generating coverage report -> [${CCOV_OUTPUT_DIR}]") + + add_custom_target( + ccov + DEPENDS ${CCOV_INDEX_FILE} ${SELF_EXE}) + + # OPTIONAL: quietly skip this step if ccov report not generated + install( + DIRECTORY ${CCOV_OUTPUT_DIR} + FILE_PERMISSIONS OWNER_READ GROUP_READ WORLD_READ + DESTINATION ${CCOV_INSTALL_DOCDIR} + COMPONENT Documentation + OPTIONAL) +endif() + +# ---------------------------------------------------------------- +# dependencies.. + +xo_self_headeronly_dependency(${SELF_EXE} xo_ratio) +xo_dependency(${SELF_EXE} randomgen) +xo_dependency(${SELF_EXE} indentlog) +xo_external_target_dependency(${SELF_EXE} Catch2 Catch2::Catch2) diff --git a/utest/ratio.test.cpp b/utest/ratio.test.cpp new file mode 100644 index 00000000..918f9b50 --- /dev/null +++ b/utest/ratio.test.cpp @@ -0,0 +1,235 @@ +/** @file ratio.utest.cpp **/ + +#include "xo/ratio/ratio.hpp" +#include "xo/ratio/ratio_iostream.hpp" +#include "xo/randomgen/random_seed.hpp" +#include "xo/randomgen/xoshiro256.hpp" +#include "xo/indentlog/scope.hpp" +#include "xo/indentlog/print/vector.hpp" +#include "xo/indentlog/print/array.hpp" +#include "xo/indentlog/print/tag.hpp" +//#include "xo/indentlog/print/hex.hpp" +#include +#include +#include + +namespace xo { + + using std::exponential_distribution; + using std::bernoulli_distribution; + + namespace ut { + template + struct ratio_distribution { + ratio_distribution(double sign_prob, double int_lambda) + : sign_dist_{sign_prob}, int_dist_{int_lambda} {} + + template + xo::ratio::ratio + random_ratio(Rng & rng) { + Int num_sign = sign_dist_(rng) ? -1 : +1; + Int num = num_sign * (1 + int_dist_(rng)); + Int den_sign = sign_dist_(rng) ? -1 : +1; + Int den = den_sign * (1 + int_dist_(rng)); + + return xo::ratio::ratio(num, den).reduce(); + } + + template + xo::ratio::ratio operator()(Rng & rng) { + return random_ratio(rng); + } + + /* generate negative numbers some of the time */ + bernoulli_distribution sign_dist_; + /* create ratios involving integers, but don't need integers to be too large */ + exponential_distribution int_dist_; + }; + + template + void + ratio_tests(Rng & rng) + { + constexpr bool debug_flag = true; + + std::size_t n_ratio = 25; + std::size_t n_experiment = n_ratio * n_ratio / 4; + /* want to avoid integer overflow when exponentiating */ + constexpr int max_pwr = 5; + + scope log(XO_DEBUG2(debug_flag, "ratio_tests")); + log && log(xtag("n_ratio", n_ratio)); + + ratio_distribution ratio_dist(0.25 /*sign_prob*/, + 0.05 /*lambda */); + bernoulli_distribution sign_dist(0.5); + exponential_distribution power_dist(0.2 /*lambda*/); + + std::vector> ratio_v; + + /* ensure 0, 1, -1 all present */ + ratio_v.push_back(xo::ratio::ratio(0,1)); + ratio_v.push_back(xo::ratio::ratio(1,1)); + ratio_v.push_back(xo::ratio::ratio(-1,1)); + + for (std::uint32_t i=0, n=n_ratio - ratio_v.size(); i(ratio1.den()); + double ratio2_approx = ratio2.num() / static_cast(ratio2.den()); + + { + auto sum = ratio1 + ratio2; + + double sum_approx = sum.num() / static_cast(sum.den()); + + log && log(XTAG(ratio1), XTAG(ratio2), XTAG(sum)); + + REQUIRE(sum_approx == Approx(ratio1_approx + ratio2_approx).epsilon(1e-6)); + REQUIRE(std::gcd(sum.num(), sum.den()) == 1); + REQUIRE(sum.den() > 0); + + /* comparison tests. piggyback on sum */ + { + auto cmp_approx = (sum_approx <=> ratio1_approx); + REQUIRE(cmp_approx == (sum <=> ratio1)); + } + { + bool eq = (sum == ratio1); + bool eq_approx = (sum_approx == ratio1_approx); + REQUIRE(eq == eq_approx); + } + { + bool ne = (sum != ratio1); + bool ne_approx = (sum_approx != ratio1_approx); + REQUIRE(ne == ne_approx); + } + { + bool gt = (sum > ratio1); + bool gt_approx = (sum_approx > ratio1_approx); + REQUIRE(gt == gt_approx); + } + { + bool ge = (sum >= ratio1); + bool ge_approx = (sum_approx >= ratio1_approx); + REQUIRE(ge == ge_approx); + } + { + bool lt = (sum > ratio1); + bool lt_approx = (sum_approx > ratio1_approx); + REQUIRE(lt == lt_approx); + } + { + bool le = (sum >= ratio1); + bool le_approx = (sum_approx >= ratio1_approx); + REQUIRE(le == le_approx); + } + } + + { + auto neg = -ratio1; + + double neg_approx = neg.num() / static_cast(neg.den()); + + log && log(XTAG(ratio1), XTAG(neg)); + + REQUIRE(neg_approx == Approx(-ratio1_approx).epsilon(1e-06)); + REQUIRE(std::gcd(neg.num(), neg.den()) == 1); + REQUIRE(neg.den() > 0); + } + + { + auto diff = ratio1 - ratio2; + + double diff_approx = diff.num() / static_cast(diff.den()); + + log && log(XTAG(ratio1), XTAG(ratio2), XTAG(diff)); + + REQUIRE(diff_approx == Approx(ratio1_approx - ratio2_approx).epsilon(1e-6)); + REQUIRE(std::gcd(diff.num(), diff.den()) == 1); + REQUIRE(diff.den() > 0); + } + + { + auto prod = ratio1 * ratio2; + + double prod_approx = prod.num() / static_cast(prod.den()); + + log && log(XTAG(ratio1), XTAG(ratio2), XTAG(prod)); + + REQUIRE(prod_approx == Approx(ratio1_approx * ratio2_approx).epsilon(1e-6)); + REQUIRE(std::gcd(prod.num(), prod.den()) == 1); + REQUIRE(prod.den() > 0); + } + + { + auto div = ratio1 * ratio2; + + double div_approx = div.num() / static_cast(div.den()); + + log && log(XTAG(ratio1), XTAG(ratio2), XTAG(div)); + + REQUIRE(div_approx == Approx(ratio1_approx * ratio2_approx).epsilon(1e-6)); + REQUIRE(std::gcd(div.num(), div.den()) == 1); + REQUIRE(div.den() > 0); + } + + { + int exp = (sign_dist(rng) ? -1 : +1) * power_dist(rng); + + if (std::abs(exp) >= max_pwr) { + exp = (std::signbit(exp) ? -1 : +1) * max_pwr; + } + + auto pwr = ratio1.power(exp); + + double pwr_approx = pwr.num() / static_cast(pwr.den()); + + log && log(XTAG(ratio1), XTAG(exp), XTAG(pwr)); + + REQUIRE(pwr_approx == Approx(::pow(ratio1_approx, exp)).epsilon(1e-6)); + REQUIRE(std::gcd(pwr.num(), pwr.den()) == 1); + REQUIRE(pwr.den() >= 0); + } + + { + auto ratio1_str = ratio1.template to_str<20>(); + + log && log(XTAG(ratio1_str)); + } + } + + } + + TEST_CASE("ratio", "[ratio]") { + //constexpr bool c_debug_flag = false; + + // can get bits from /dev/random by uncommenting the 2nd line below + uint64_t seed = 5521646833469436535ul; + //rng::Seed seed; + + //std::cerr << "ratio: seed=" << seed << std::endl; + + auto rng = rng::xoshiro256ss(seed); + + ratio_tests(rng); + } /*TEST_CASE(ratio)*/ + + } /*namespace ut*/ + +} /*namespace xo*/ + + +/** end ratio.utest.cpp **/ diff --git a/utest/ratio_utest_main.cpp b/utest/ratio_utest_main.cpp new file mode 100644 index 00000000..1bf5bace --- /dev/null +++ b/utest/ratio_utest_main.cpp @@ -0,0 +1,6 @@ +/** @file ratio_utest_main.cpp **/ + +#define CATCH_CONFIG_MAIN +#include "catch2/catch.hpp" + +/** end ratio_utest_main.cpp **/