Skip to content

Commit

Permalink
Add various signed integer negation functions
Browse files Browse the repository at this point in the history
Specifically,

 * num::unchecked_neg
 * num::wrapping_neg
 * num::overflowing_neg
 * num::checked_neg
 * num::neg

Each call to `xxx_neg(val)` is semantically the same as `xxx_sub(0, val)`, but is shorter to spell and may generate better code.
  • Loading branch information
tcbrindle committed Aug 29, 2024
1 parent 9388588 commit d5ec687
Show file tree
Hide file tree
Showing 6 changed files with 275 additions and 0 deletions.
58 changes: 58 additions & 0 deletions include/flux/core/numeric.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,15 @@ struct unchecked_shr_fn {
}
};

struct unchecked_neg_fn {
template <signed_integral T>
[[nodiscard]]
constexpr auto operator()(T val) const noexcept -> T
{
return static_cast<T>(-val);
}
};

struct wrapping_add_fn {
template <integral T>
[[nodiscard]]
Expand Down Expand Up @@ -190,6 +199,16 @@ struct wrapping_mul_fn {
}
};

struct wrapping_neg_fn {
template <signed_integral T>
[[nodiscard]]
constexpr auto operator()(T val) const noexcept -> T
{
using U = std::make_unsigned_t<T>;
return static_cast<T>(static_cast<U>(0) - static_cast<U>(val));
}
};

#if defined(__has_builtin)
# if __has_builtin(__builtin_add_overflow) && \
__has_builtin(__builtin_sub_overflow) && \
Expand Down Expand Up @@ -306,6 +325,20 @@ struct overflowing_mul_fn {
}
};

struct overflowing_neg_fn {
template <signed_integral T>
[[nodiscard]]
constexpr auto operator()(T val) const noexcept -> overflow_result<T>
{
if constexpr (use_builtin_overflow_ops) {
bool o = __builtin_sub_overflow(T{0}, val, &val);
return {val, o};
} else {
return {wrapping_neg_fn{}(val), val == std::numeric_limits<T>::lowest()};
}
}
};

struct checked_add_fn {
template <integral T>
[[nodiscard]]
Expand Down Expand Up @@ -458,6 +491,23 @@ struct checked_shr_fn {
}
};

struct checked_neg_fn {
template <signed_integral T>
[[nodiscard]]
constexpr auto operator()(T val,
std::source_location loc = std::source_location::current()) const
-> T
{
// If T is at least as large as int, we already get a check
// when in constant evaluation
if ((!std::is_constant_evaluated() || (sizeof(T) < sizeof(int))) &&
val == std::numeric_limits<T>::lowest()) {
flux::runtime_error("overflow in signed negation", loc);
}
return unchecked_neg_fn{}(val);
}
};

template <overflow_policy>
struct default_ops;

Expand All @@ -466,6 +516,7 @@ struct default_ops<overflow_policy::ignore> {
using add_fn = unchecked_add_fn;
using sub_fn = unchecked_sub_fn;
using mul_fn = unchecked_mul_fn;
using neg_fn = unchecked_neg_fn;
using shl_fn = unchecked_shl_fn;
using shr_fn = unchecked_shr_fn;
};
Expand All @@ -475,6 +526,7 @@ struct default_ops<overflow_policy::wrap> {
using add_fn = wrapping_add_fn;
using sub_fn = wrapping_sub_fn;
using mul_fn = wrapping_mul_fn;
using neg_fn = wrapping_neg_fn;
// no wrapping versions of these yet, so use the checked versions
using shl_fn = checked_shl_fn;
using shr_fn = checked_shr_fn;
Expand All @@ -485,6 +537,7 @@ struct default_ops<overflow_policy::error> {
using add_fn = checked_add_fn;
using sub_fn = checked_sub_fn;
using mul_fn = checked_mul_fn;
using neg_fn = checked_neg_fn;
using shl_fn = checked_shl_fn;
using shr_fn = checked_shr_fn;
};
Expand Down Expand Up @@ -512,22 +565,26 @@ FLUX_EXPORT inline constexpr auto unchecked_sub = detail::unchecked_sub_fn{};
FLUX_EXPORT inline constexpr auto unchecked_mul = detail::unchecked_mul_fn{};
FLUX_EXPORT inline constexpr auto unchecked_div = detail::unchecked_div_fn{};
FLUX_EXPORT inline constexpr auto unchecked_mod = detail::unchecked_mod_fn{};
FLUX_EXPORT inline constexpr auto unchecked_neg = detail::unchecked_neg_fn{};
FLUX_EXPORT inline constexpr auto unchecked_shl = detail::unchecked_shl_fn{};
FLUX_EXPORT inline constexpr auto unchecked_shr = detail::unchecked_shr_fn{};

FLUX_EXPORT inline constexpr auto wrapping_add = detail::wrapping_add_fn{};
FLUX_EXPORT inline constexpr auto wrapping_sub = detail::wrapping_sub_fn{};
FLUX_EXPORT inline constexpr auto wrapping_mul = detail::wrapping_mul_fn{};
FLUX_EXPORT inline constexpr auto wrapping_neg = detail::wrapping_neg_fn{};

FLUX_EXPORT inline constexpr auto overflowing_add = detail::overflowing_add_fn{};
FLUX_EXPORT inline constexpr auto overflowing_sub = detail::overflowing_sub_fn{};
FLUX_EXPORT inline constexpr auto overflowing_mul = detail::overflowing_mul_fn{};
FLUX_EXPORT inline constexpr auto overflowing_neg = detail::overflowing_neg_fn{};

FLUX_EXPORT inline constexpr auto checked_add = detail::checked_add_fn{};
FLUX_EXPORT inline constexpr auto checked_sub = detail::checked_sub_fn{};
FLUX_EXPORT inline constexpr auto checked_mul = detail::checked_mul_fn{};
FLUX_EXPORT inline constexpr auto checked_div = detail::checked_div_fn{};
FLUX_EXPORT inline constexpr auto checked_mod = detail::checked_mod_fn{};
FLUX_EXPORT inline constexpr auto checked_neg = detail::checked_neg_fn{};
FLUX_EXPORT inline constexpr auto checked_shl = detail::checked_shl_fn{};
FLUX_EXPORT inline constexpr auto checked_shr = detail::checked_shr_fn{};

Expand All @@ -538,6 +595,7 @@ FLUX_EXPORT inline constexpr auto div =
detail::checked_div_fn<config::on_overflow, config::on_divide_by_zero>{};
FLUX_EXPORT inline constexpr auto mod =
detail::checked_mod_fn<config::on_overflow, config::on_divide_by_zero>{};
FLUX_EXPORT inline constexpr auto neg = detail::default_ops<config::on_overflow>::neg_fn{};
FLUX_EXPORT inline constexpr auto shl = detail::default_ops<config::on_overflow>::shl_fn{};
FLUX_EXPORT inline constexpr auto shr = detail::default_ops<config::on_overflow>::shr_fn{};

Expand Down
54 changes: 54 additions & 0 deletions test/num/test_checked_ops.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,32 @@ static_assert(test_checked_mod_constexpr<unsigned long>());
static_assert(test_checked_mod_constexpr<signed long long>());
static_assert(test_checked_mod_constexpr<unsigned long long>());

template <typename T>
constexpr bool test_checked_neg_constexpr()
{
constexpr auto& neg = flux::num::checked_neg;

constexpr T zero = T{0};
constexpr T one = T{1};
constexpr T minus_one = T{-1};
constexpr T min = std::numeric_limits<T>::lowest();
constexpr T max = std::numeric_limits<T>::max();

// Only neg(min) overflows
STATIC_CHECK(neg(zero) == zero);
STATIC_CHECK(neg(one) == minus_one);
STATIC_CHECK(neg(minus_one) == one);
STATIC_CHECK(neg(max) == min + 1);

STATIC_CHECK(not CONSTEXPR_CALLABLE(neg(min)));

return true;
}
static_assert(test_checked_neg_constexpr<signed char>());
static_assert(test_checked_neg_constexpr<signed short>());
static_assert(test_checked_neg_constexpr<signed int>());
static_assert(test_checked_neg_constexpr<signed long>());
static_assert(test_checked_neg_constexpr<signed long long>());

template <typename T, typename U>
struct test_checked_shl_constexpr {
Expand Down Expand Up @@ -658,6 +684,25 @@ void test_checked_mod_runtime()
}
};

template <typename T>
void test_checked_neg_runtime()
{
constexpr auto& neg = flux::num::neg;

constexpr T zero = T{0};
constexpr T one = T{1};
constexpr T minus_one = T{-1};
constexpr T min = std::numeric_limits<T>::lowest();
constexpr T max = std::numeric_limits<T>::max();

// Only neg(min) overflows
REQUIRE(neg(zero) == zero);
REQUIRE(neg(one) == minus_one);
REQUIRE(neg(minus_one) == one);
REQUIRE(neg(max) == min + 1);

REQUIRE_THROWS_AS(neg(min), flux::unrecoverable_error);
}

template <typename T, typename U>
struct test_checked_shl_runtime {
Expand Down Expand Up @@ -804,6 +849,15 @@ TEST_CASE("num.checked_mod")
test_checked_mod_runtime<unsigned long long>();
}

TEST_CASE("num.checked_neg")
{
test_checked_neg_runtime<signed char>();
test_checked_neg_runtime<signed short>();
test_checked_neg_runtime<signed int>();
test_checked_neg_runtime<signed long>();
test_checked_neg_runtime<signed long long>();
}

TEST_CASE("num.checked_shl")
{
test_helper1<test_checked_shl_runtime,
Expand Down
55 changes: 55 additions & 0 deletions test/num/test_default_ops.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,33 @@ static_assert(test_mod_constexpr<unsigned long>());
static_assert(test_mod_constexpr<signed long long>());
static_assert(test_mod_constexpr<unsigned long long>());

template <typename T>
constexpr bool test_neg_constexpr()
{
constexpr auto& neg = flux::num::neg;

constexpr T zero = T{0};
constexpr T one = T{1};
constexpr T minus_one = T{-1};
constexpr T min = std::numeric_limits<T>::lowest();
constexpr T max = std::numeric_limits<T>::max();

// Only neg(min) overflows
STATIC_CHECK(neg(zero) == zero);
STATIC_CHECK(neg(one) == minus_one);
STATIC_CHECK(neg(minus_one) == one);
STATIC_CHECK(neg(max) == min + 1);

STATIC_CHECK(not CONSTEXPR_CALLABLE(neg(min)));

return true;
}
static_assert(test_neg_constexpr<signed char>());
static_assert(test_neg_constexpr<signed short>());
static_assert(test_neg_constexpr<signed int>());
static_assert(test_neg_constexpr<signed long>());
static_assert(test_neg_constexpr<signed long long>());

template <typename T, typename U>
struct test_shl_constexpr {
constexpr bool operator()() const
Expand Down Expand Up @@ -656,6 +683,25 @@ void test_mod_runtime()
}
};

template <typename T>
void test_neg_runtime()
{
constexpr auto& neg = flux::num::neg;

constexpr T zero = T{0};
constexpr T one = T{1};
constexpr T minus_one = T{-1};
constexpr T min = std::numeric_limits<T>::lowest();
constexpr T max = std::numeric_limits<T>::max();

// Only neg(min) overflows
REQUIRE(neg(zero) == zero);
REQUIRE(neg(one) == minus_one);
REQUIRE(neg(minus_one) == one);
REQUIRE(neg(max) == min + 1);

REQUIRE_THROWS_AS(neg(min), flux::unrecoverable_error);
}

template <typename T, typename U>
struct test_shl_runtime {
Expand Down Expand Up @@ -802,6 +848,15 @@ TEST_CASE("num.mod")
test_mod_runtime<unsigned long long>();
}

TEST_CASE("num.neg")
{
test_neg_runtime<signed char>();
test_neg_runtime<signed short>();
test_neg_runtime<signed int>();
test_neg_runtime<signed long>();
test_neg_runtime<signed long long>();
}

TEST_CASE("num.shl")
{
test_helper1<test_shl_runtime,
Expand Down
48 changes: 48 additions & 0 deletions test/num/test_overflowing_ops.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,46 @@ static_assert(test_overflowing_mul<unsigned long>());
static_assert(test_overflowing_mul<signed long long>());
static_assert(test_overflowing_mul<unsigned long long>());

template <typename T>
constexpr bool test_overflowing_neg()
{
constexpr auto& neg = flux::num::overflowing_neg;

constexpr T zero = T{0};
constexpr T one = T{1};
constexpr T minus_one = T{-1};
constexpr T min = std::numeric_limits<T>::lowest();
constexpr T max = std::numeric_limits<T>::max();

auto r = neg(zero);
STATIC_CHECK(r.value == zero);
STATIC_CHECK(r.overflowed == false);

r = neg(one);
STATIC_CHECK(r.value == minus_one);
STATIC_CHECK(r.overflowed == false);

r = neg(minus_one);
STATIC_CHECK(r.value == one);
STATIC_CHECK(r.overflowed == false);

r = neg(max);
STATIC_CHECK(r.value == min + 1);
STATIC_CHECK(r.overflowed == false);

r = neg(min);
STATIC_CHECK(r.value == min);
STATIC_CHECK(r.overflowed == true);

return true;
}
static_assert(test_overflowing_neg<signed char>());
static_assert(test_overflowing_neg<signed short>());
static_assert(test_overflowing_neg<signed int>());
static_assert(test_overflowing_neg<signed long>());
static_assert(test_overflowing_neg<signed long long>());


}

TEST_CASE("num.overflowing_add")
Expand Down Expand Up @@ -349,3 +389,11 @@ TEST_CASE("num.overflowing_mul")
test_overflowing_mul<unsigned long long>();
}

TEST_CASE("num.overflowing_neg")
{
test_overflowing_neg<signed char>();
test_overflowing_neg<signed short>();
test_overflowing_neg<signed int>();
test_overflowing_neg<signed long>();
test_overflowing_neg<signed long long>();
}
29 changes: 29 additions & 0 deletions test/num/test_unchecked_ops.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,26 @@ void test_unchecked_sub()
REQUIRE(sub(T(-1), max) == min);
}

template <typename T>
void test_unchecked_neg()
{
constexpr auto& neg = flux::num::unchecked_neg;

constexpr T zero = 0;
constexpr T one = 1;
constexpr T minus_one = -1;
constexpr T min = std::numeric_limits<T>::lowest();
constexpr T max = std::numeric_limits<T>::max();

REQUIRE(neg(zero) == zero);
REQUIRE(neg(one) == minus_one);
REQUIRE(neg(minus_one) == one);
REQUIRE(neg(neg(one)) == one);
REQUIRE(neg(neg(minus_one)) == minus_one);
REQUIRE(neg(max) == min + 1);
REQUIRE(neg(min + 1) == max);
}

template <typename T>
void test_unchecked_mul()
{
Expand Down Expand Up @@ -228,6 +248,15 @@ TEST_CASE("num.unchecked_sub")
test_unchecked_sub<unsigned long long>();
}

TEST_CASE("num.unchecked_neg")
{
test_unchecked_neg<signed char>();
test_unchecked_neg<signed short>();
test_unchecked_neg<signed int>();
test_unchecked_neg<signed long>();
test_unchecked_neg<signed long long>();
}

TEST_CASE("num.unchecked_mul")
{
test_unchecked_mul<signed char>();
Expand Down
Loading

0 comments on commit d5ec687

Please sign in to comment.