/** @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 **/