Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion intdiv.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
#define INTDIV_HPP

#include <compare>
#include <type_traits>
#include <concepts>
#include <type_traits>

using __suppress_unused_include_compare_warning = std::strong_ordering;

Expand Down Expand Up @@ -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<T> 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<T>) {
using U = std::make_unsigned_t<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<T> div_rem_to_odd(T x, T y) {
T quotient_sign = __sgn2(x) * __sgn2(y);
Expand Down
110 changes: 108 additions & 2 deletions test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,21 @@ constexpr bool is_valid_division_to_neg_inf(T x, T y, T q, T r) {
}
}

template<class T>
constexpr bool is_valid_euclid_remainder(T y, T r) {
if constexpr (std::is_signed_v<T>) {
return r >= 0 && std::abs(big_int(r)) < std::abs(big_int(y));
} else {
return r < y;
}
}

template<class T>
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<class T>
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;
Expand Down Expand Up @@ -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<int>{-3, 1});
static_assert(div_rem_euclid(-8, -3) == div_result<int>{3, 1});
static_assert(div_rem_euclid(8, -3) == div_result<int>{-2, 2});
static_assert(div_euclid(-8, 3) == -3);
static_assert(rem_euclid(8, -3) == 2);

template<class T>
constexpr bool is_valid_division_ties_to_zero(T x, T y, T q, T r) {
Expand Down Expand Up @@ -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<class T, int SignedValue>
constexpr T signed_or_zero() {
if constexpr (std::is_signed_v<T>)
return T(SignedValue);
else
return T(0);
}

template<class T>
inline auto interesting_values = [] {
if constexpr (std::is_signed_v<T>) {
Expand Down Expand Up @@ -242,10 +270,12 @@ void fuzz_test(std::string_view name) {

std::default_random_engine rng{12345};

std::uniform_int_distribution<T> distr_tiny{std::is_signed_v<T> ? -4 : 0, 4};
constexpr T tiny_min = signed_or_zero<T, -4>();
std::uniform_int_distribution<T> distr_tiny{tiny_min, T(4)};
sample<T, div_rem, verify>(rng, distr_tiny, 100);

std::uniform_int_distribution<T> distr_small{std::is_signed_v<T> ? -100 : 0, 100};
constexpr T small_min = signed_or_zero<T, -100>();
std::uniform_int_distribution<T> distr_small{small_min, T(100)};
sample<T, div_rem, verify>(rng, distr_small, 100'000);

std::uniform_int_distribution<T> distr_full;
Expand All @@ -272,13 +302,84 @@ void fuzz_test_mod(std::string_view name) {
std::cout << "OK" << std::endl;
}

template<class T>
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<T>) {
for (const T& y : interesting_values<T>) {
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<T, -4>();
std::uniform_int_distribution<T> 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<T> 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<class T>
void fuzz_test_rem_euclid(std::string_view name) {
std::cout << name << " ... " << std::flush;
std::default_random_engine rng{12345};
std::uniform_int_distribution<T> 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<type, div_rem, verify>(#div_rem "<" #type ">")

int main() {
RUN_TEST(int, div_rem_to_zero, is_valid_division_to_zero);
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);

Expand All @@ -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);

Expand All @@ -305,4 +407,8 @@ int main() {

fuzz_test_mod<int>("mod<int>");
fuzz_test_mod<unsigned>("mod<unsigned>");
fuzz_test_euclid_projection<int>("euclid projections<int>");
fuzz_test_euclid_projection<unsigned>("euclid projections<unsigned>");
fuzz_test_rem_euclid<int>("rem_euclid<int>");
fuzz_test_rem_euclid<unsigned>("rem_euclid<unsigned>");
}
Loading