xo-ratio: + unit test
This commit is contained in:
parent
c0b2e83211
commit
d907f4eaff
3 changed files with 298 additions and 0 deletions
57
utest/CMakeLists.txt
Normal file
57
utest/CMakeLists.txt
Normal file
|
|
@ -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)
|
||||||
235
utest/ratio.test.cpp
Normal file
235
utest/ratio.test.cpp
Normal file
|
|
@ -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 <catch2/catch.hpp>
|
||||||
|
#include <random>
|
||||||
|
#include <numeric>
|
||||||
|
|
||||||
|
namespace xo {
|
||||||
|
|
||||||
|
using std::exponential_distribution;
|
||||||
|
using std::bernoulli_distribution;
|
||||||
|
|
||||||
|
namespace ut {
|
||||||
|
template <typename Int>
|
||||||
|
struct ratio_distribution {
|
||||||
|
ratio_distribution(double sign_prob, double int_lambda)
|
||||||
|
: sign_dist_{sign_prob}, int_dist_{int_lambda} {}
|
||||||
|
|
||||||
|
template <typename Rng>
|
||||||
|
xo::ratio::ratio<Int>
|
||||||
|
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 <typename Rng>
|
||||||
|
xo::ratio::ratio<Int> 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<double> int_dist_;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Rng>
|
||||||
|
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<int> ratio_dist(0.25 /*sign_prob*/,
|
||||||
|
0.05 /*lambda */);
|
||||||
|
bernoulli_distribution sign_dist(0.5);
|
||||||
|
exponential_distribution<double> power_dist(0.2 /*lambda*/);
|
||||||
|
|
||||||
|
std::vector<xo::ratio::ratio<int>> ratio_v;
|
||||||
|
|
||||||
|
/* ensure 0, 1, -1 all present */
|
||||||
|
ratio_v.push_back(xo::ratio::ratio<int>(0,1));
|
||||||
|
ratio_v.push_back(xo::ratio::ratio<int>(1,1));
|
||||||
|
ratio_v.push_back(xo::ratio::ratio<int>(-1,1));
|
||||||
|
|
||||||
|
for (std::uint32_t i=0, n=n_ratio - ratio_v.size(); i<n; ++i) {
|
||||||
|
ratio_v.push_back(ratio_dist(rng));
|
||||||
|
|
||||||
|
REQUIRE(std::gcd(ratio_v[i].num(), ratio_v[i].den()) == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
INFO(XTAG(ratio_v));
|
||||||
|
|
||||||
|
for (std::uint32_t i=0; i<n_experiment; ++i) {
|
||||||
|
INFO(tostr(XTAG(i), XTAG(n_experiment)));
|
||||||
|
|
||||||
|
/* choose a couple of ratios at random */
|
||||||
|
auto ratio1 = ratio_v[rng() % n_ratio];
|
||||||
|
auto ratio2 = ratio_v[rng() % n_ratio];
|
||||||
|
|
||||||
|
double ratio1_approx = ratio1.num() / static_cast<double>(ratio1.den());
|
||||||
|
double ratio2_approx = ratio2.num() / static_cast<double>(ratio2.den());
|
||||||
|
|
||||||
|
{
|
||||||
|
auto sum = ratio1 + ratio2;
|
||||||
|
|
||||||
|
double sum_approx = sum.num() / static_cast<double>(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<double>(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<double>(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<double>(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<double>(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<double>(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<rng::xoshiro256ss> 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 **/
|
||||||
6
utest/ratio_utest_main.cpp
Normal file
6
utest/ratio_utest_main.cpp
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
/** @file ratio_utest_main.cpp **/
|
||||||
|
|
||||||
|
#define CATCH_CONFIG_MAIN
|
||||||
|
#include "catch2/catch.hpp"
|
||||||
|
|
||||||
|
/** end ratio_utest_main.cpp **/
|
||||||
Loading…
Add table
Add a link
Reference in a new issue