diff --git a/intdiv.hpp b/intdiv.hpp index 0fd304b..8a9d968 100644 --- a/intdiv.hpp +++ b/intdiv.hpp @@ -2,8 +2,8 @@ #define INTDIV_HPP #include -#include #include +#include using __suppress_unused_include_compare_warning = std::strong_ordering; @@ -105,6 +105,28 @@ constexpr T div_to_neg_inf(T x, T y) { return div_rem_to_neg_inf(x, y).quotient; } +template<__integer T> +constexpr div_result div_rem_euclid(T x, T y) { + T quotient_sign = __sgn2(x) * __sgn2(y); + bool increment = x % y < 0; + return __div_rem_offset_quotient(x, y, T(increment) * quotient_sign); +} + +template<__integer T> +constexpr T div_euclid(T x, T y) { + return div_rem_euclid(x, y).quotient; +} + +template<__integer T> +constexpr T rem_euclid(T x, T y) { + if constexpr (std::is_signed_v) { + using U = std::make_unsigned_t; + return T(U(x % y) + U(x % y < 0) * U(__sgn2(y)) * U(y)); + } else { + return x % y; + } +} + template<__integer T> constexpr div_result div_rem_to_odd(T x, T y) { T quotient_sign = __sgn2(x) * __sgn2(y); diff --git a/test.cpp b/test.cpp index 84516a0..077628b 100644 --- a/test.cpp +++ b/test.cpp @@ -77,6 +77,21 @@ constexpr bool is_valid_division_to_neg_inf(T x, T y, T q, T r) { } } +template +constexpr bool is_valid_euclid_remainder(T y, T r) { + if constexpr (std::is_signed_v) { + return r >= 0 && std::abs(big_int(r)) < std::abs(big_int(y)); + } else { + return r < y; + } +} + +template +constexpr bool is_valid_division_euclid(T x, T y, T q, T r) { + if (!is_valid_division(x, y, q, r)) return false; + return is_valid_euclid_remainder(y, r); +} + template constexpr bool is_valid_division_to_odd(T x, T y, T q, T r) { if (!is_valid_division(x, y, q, r)) return false; @@ -115,6 +130,11 @@ static_assert(div_rem_away_zero(72'777'531u, 3'405'476'348u).quotient == 1); static_assert(72'777'531u == 1u * 3'405'476'348u + 962'268'479u); static_assert(div_rem_away_zero(72'777'531u, 3'405'476'348u).remainder == 962'268'479u); static_assert(div_rem_to_pos_inf(72'777'531u, 3'405'476'348u).remainder == 962'268'479u); +static_assert(div_rem_euclid(-8, 3) == div_result{-3, 1}); +static_assert(div_rem_euclid(-8, -3) == div_result{3, 1}); +static_assert(div_rem_euclid(8, -3) == div_result{-2, 2}); +static_assert(div_euclid(-8, 3) == -3); +static_assert(rem_euclid(8, -3) == 2); template constexpr bool is_valid_division_ties_to_zero(T x, T y, T q, T r) { @@ -166,6 +186,14 @@ constexpr bool is_valid_division_ties_to_even(T x, T y, T q, T r) { using rng_type = std::default_random_engine; +template +constexpr T signed_or_zero() { + if constexpr (std::is_signed_v) + return T(SignedValue); + else + return T(0); +} + template inline auto interesting_values = [] { if constexpr (std::is_signed_v) { @@ -242,10 +270,12 @@ void fuzz_test(std::string_view name) { std::default_random_engine rng{12345}; - std::uniform_int_distribution distr_tiny{std::is_signed_v ? -4 : 0, 4}; + constexpr T tiny_min = signed_or_zero(); + std::uniform_int_distribution distr_tiny{tiny_min, T(4)}; sample(rng, distr_tiny, 100); - std::uniform_int_distribution distr_small{std::is_signed_v ? -100 : 0, 100}; + constexpr T small_min = signed_or_zero(); + std::uniform_int_distribution distr_small{small_min, T(100)}; sample(rng, distr_small, 100'000); std::uniform_int_distribution distr_full; @@ -272,6 +302,76 @@ void fuzz_test_mod(std::string_view name) { std::cout << "OK" << std::endl; } +template +void fuzz_test_euclid_projection(std::string_view name) { + std::cout << name << " ... " << std::flush; + auto verify = [](T x, T y) { + const auto result = div_rem_euclid(x, y); + return div_euclid(x, y) == result.quotient && rem_euclid(x, y) == result.remainder; + }; + + for (const T& x : interesting_values) { + for (const T& y : interesting_values) { + if (!is_div_defined(x, y)) continue; + if (!verify(x, y)) { + std::cout << "failed for (" << x << " / " << y << ")\n"; + std::exit(1); + } + } + } + + std::default_random_engine rng{12345}; + + constexpr T tiny_min = signed_or_zero(); + std::uniform_int_distribution distr_tiny{tiny_min, T(4)}; + for (int i = 0; i < 100; ++i) { + const T x = distr_tiny(rng); + const T y = distr_tiny(rng); + if (!is_div_defined(x, y)) continue; + if (!verify(x, y)) { + std::cout << "failed for (" << x << " / " << y << ")\n"; + std::exit(1); + } + } + + std::uniform_int_distribution distr_full; + for (int i = 0; i < full_samples; ++i) { + const T x = distr_full(rng); + const T y = distr_full(rng); + if (!is_div_defined(x, y)) continue; + if (!verify(x, y)) { + std::cout << "failed for (" << x << " / " << y << ")\n"; + std::exit(1); + } + } + + std::cout << "OK" << std::endl; +} + +template +void fuzz_test_rem_euclid(std::string_view name) { + std::cout << name << " ... " << std::flush; + std::default_random_engine rng{12345}; + std::uniform_int_distribution distr_full; + + for (int i = 0; i < full_samples; ++i) { + T x = distr_full(rng); + T y = distr_full(rng); + if (!is_div_defined(x, y)) continue; + const auto result = div_rem_euclid(x, y); + T r = rem_euclid(x, y); + if (r != result.remainder) { + std::cout << "failure for (" << x << " rem_euclid " << y << ") = " << r << '\n'; + std::exit(1); + } + if (!is_valid_euclid_remainder(y, r)) { + std::cout << "failure for (" << x << " rem_euclid " << y << ") = " << r << '\n'; + std::exit(1); + } + } + std::cout << "OK" << std::endl; +} + #define RUN_TEST(type, div_rem, verify) fuzz_test(#div_rem "<" #type ">") int main() { @@ -279,6 +379,7 @@ int main() { RUN_TEST(int, div_rem_away_zero, is_valid_division_away_zero); RUN_TEST(int, div_rem_to_pos_inf, is_valid_division_to_pos_inf); RUN_TEST(int, div_rem_to_neg_inf, is_valid_division_to_neg_inf); + RUN_TEST(int, div_rem_euclid, is_valid_division_euclid); RUN_TEST(int, div_rem_to_odd, is_valid_division_to_odd); RUN_TEST(int, div_rem_to_even, is_valid_division_to_even); @@ -293,6 +394,7 @@ int main() { RUN_TEST(unsigned, div_rem_away_zero, is_valid_division_away_zero); RUN_TEST(unsigned, div_rem_to_pos_inf, is_valid_division_to_pos_inf); RUN_TEST(unsigned, div_rem_to_neg_inf, is_valid_division_to_neg_inf); + RUN_TEST(unsigned, div_rem_euclid, is_valid_division_euclid); RUN_TEST(unsigned, div_rem_to_odd, is_valid_division_to_odd); RUN_TEST(unsigned, div_rem_to_even, is_valid_division_to_even); @@ -305,4 +407,8 @@ int main() { fuzz_test_mod("mod"); fuzz_test_mod("mod"); + fuzz_test_euclid_projection("euclid projections"); + fuzz_test_euclid_projection("euclid projections"); + fuzz_test_rem_euclid("rem_euclid"); + fuzz_test_rem_euclid("rem_euclid"); }