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