Skip to content

Add mixed signed/unsigned math #60

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jul 17, 2022
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
15 changes: 14 additions & 1 deletion num/__private/intrinsics.h
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,18 @@ sus_always_inline
};
}

template <class T, class U = decltype(to_signed(std::declval<T>()))>
requires(std::is_integral_v<T> && !std::is_signed_v<T> && sizeof(T) <= 8 &&
sizeof(T) == sizeof(U))
sus_always_inline
constexpr OverflowOut<T> add_with_overflow_signed(T x, U y) noexcept {
return OverflowOut<T>{
.overflow = (y >= 0 && into_unsigned(y) > max_value<T>() - x) ||
(y < 0 && into_unsigned(-y) > x),
.value = x + into_unsigned(y),
};
}

template <class T, class U = decltype(to_unsigned(std::declval<T>()))>
requires(std::is_integral_v<T> && std::is_signed_v<T> && sizeof(T) <= 8 &&
sizeof(T) == sizeof(U))
Expand Down Expand Up @@ -478,7 +490,8 @@ sus_always_inline
// TODO: Can we use compiler intrinsics?
auto out = into_widened(x) * into_widened(y);
using Wide = decltype(out);
return OverflowOut{.overflow = out > Wide{max_value<T>()}, .value = static_cast<T>(out)};
return OverflowOut{.overflow = out > Wide{max_value<T>()},
.value = static_cast<T>(out)};
}

template <class T>
Expand Down
63 changes: 62 additions & 1 deletion num/__private/signed_integer_macros.h
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,19 @@
return Option<T>::none(); \
} \
\
/** Checked integer addition with an unsigned rhs. Computes self + rhs, \
* returning None if overflow occurred. \
*/ \
constexpr Option<T> checked_add_unsigned(const UnsignedT& rhs) \
const& noexcept { \
const auto out = \
__private::add_with_overflow_unsigned(primitive_value, rhs); \
if (!out.overflow) [[likely]] \
return Option<T>::some(out.value); \
else \
return Option<T>::none(); \
} \
\
/** Calculates self + rhs \
* \
* Returns a tuple of the addition along with a boolean indicating whether \
Expand Down Expand Up @@ -422,6 +435,18 @@
return __private::saturating_add(primitive_value, rhs.primitive_value); \
} \
\
/** Saturating integer addition with an unsigned rhs. Computes self + rhs, \
* saturating at the numeric bounds instead of overflowing. \
*/ \
constexpr T saturating_add_unsigned(const UnsignedT& rhs) const& noexcept { \
const auto r = \
__private::add_with_overflow_unsigned(primitive_value, rhs); \
if (!r.overflow) [[likely]] \
return r.value; \
else \
return MAX(); \
} \
\
/** Unchecked integer addition. Computes self + rhs, assuming overflow \
* cannot occur. \
* \
Expand All @@ -439,6 +464,13 @@
*/ \
constexpr T wrapping_add(const T& rhs) const& noexcept { \
return __private::wrapping_add(primitive_value, rhs.primitive_value); \
} \
\
/** Wrapping (modular) addition with an unsigned rhs. Computes self + rhs, \
* wrapping around at the boundary of the type. \
*/ \
constexpr T wrapping_add_unsigned(const UnsignedT& rhs) const& noexcept { \
return __private::add_with_overflow_unsigned(primitive_value, rhs).value; \
} \
static_assert(true)

Expand Down Expand Up @@ -773,6 +805,17 @@
return Option<T>::none(); \
} \
\
/** Checked integer subtraction with an unsigned rhs. Computes self - rhs, \
* returning None if overflow occurred. \
*/ \
constexpr Option<T> checked_sub_unsigned(const UnsignedT& rhs) const& { \
auto out = __private::sub_with_overflow_unsigned(primitive_value, rhs); \
if (!out.overflow) [[likely]] \
return Option<T>::some(out.value); \
else \
return Option<T>::none(); \
} \
\
/** Calculates self - rhs \
* \
* Returns a tuple of the subtraction along with a boolean indicating \
Expand All @@ -785,7 +828,7 @@
return Tuple<T, bool>::with(r.value, r.overflow); \
} \
\
/** Calculates self - rhs \
/** Calculates self - rhs with an unsigned rhs. \
* \
* Returns a tuple of the subtraction along with a boolean indicating \
* whether an arithmetic overflow would occur. If an overflow would have \
Expand All @@ -804,6 +847,17 @@
return __private::saturating_sub(primitive_value, rhs.primitive_value); \
} \
\
/** Saturating integer subtraction with an unsigned rhs. Computes self - \
* rhs, saturating at the numeric bounds instead of overflowing. \
*/ \
constexpr T saturating_sub_unsigned(const UnsignedT& rhs) const& { \
auto r = __private::sub_with_overflow_unsigned(primitive_value, rhs); \
if (!r.overflow) [[likely]] \
return r.value; \
else \
return MIN(); \
} \
\
/** Unchecked integer subtraction. Computes self - rhs, assuming overflow \
* cannot occur. \
*/ \
Expand All @@ -817,6 +871,13 @@
*/ \
constexpr T wrapping_sub(const T& rhs) const& { \
return __private::wrapping_sub(primitive_value, rhs.primitive_value); \
} \
\
/** Wrapping (modular) subtraction with an unsigned rhs. Computes self - \
* rhs, wrapping around at the boundary of the type. \
*/ \
constexpr T wrapping_sub_unsigned(const UnsignedT& rhs) const& { \
return __private::sub_with_overflow_unsigned(primitive_value, rhs).value; \
} \
static_assert(true)

Expand Down
96 changes: 76 additions & 20 deletions num/__private/unsigned_integer_macros.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,25 +36,25 @@
} \
static_assert(true)

#define _sus__unsigned_impl(T, Bytes, LargerT) \
_sus__unsigned_from(T); \
_sus__unsigned_integer_comparison(T); \
_sus__unsigned_unary_ops(T); \
_sus__unsigned_binary_logic_ops(T); \
_sus__unsigned_binary_bit_ops(T); \
_sus__unsigned_mutable_logic_ops(T); \
_sus__unsigned_mutable_bit_ops(T); \
_sus__unsigned_abs(T); \
_sus__unsigned_add(T); \
_sus__unsigned_div(T); \
_sus__unsigned_mul(T, LargerT); \
_sus__unsigned_neg(T); \
_sus__unsigned_rem(T); \
_sus__unsigned_shift(T); \
_sus__unsigned_sub(T); \
_sus__unsigned_bits(T); \
_sus__unsigned_pow(T); \
_sus__unsigned_log(T); \
#define _sus__unsigned_impl(T, Bytes, SignedT, LargerT) \
_sus__unsigned_from(T); \
_sus__unsigned_integer_comparison(T); \
_sus__unsigned_unary_ops(T); \
_sus__unsigned_binary_logic_ops(T); \
_sus__unsigned_binary_bit_ops(T); \
_sus__unsigned_mutable_logic_ops(T); \
_sus__unsigned_mutable_bit_ops(T); \
_sus__unsigned_abs(T); \
_sus__unsigned_add(T, SignedT); \
_sus__unsigned_div(T); \
_sus__unsigned_mul(T, LargerT); \
_sus__unsigned_neg(T); \
_sus__unsigned_rem(T); \
_sus__unsigned_shift(T); \
_sus__unsigned_sub(T); \
_sus__unsigned_bits(T); \
_sus__unsigned_pow(T); \
_sus__unsigned_log(T); \
_sus__unsigned_endian(T, Bytes)

#define _sus__unsigned_from(T) \
Expand Down Expand Up @@ -248,7 +248,7 @@
} \
static_assert(true)

#define _sus__unsigned_add(T) \
#define _sus__unsigned_add(T, SignedT) \
/** Checked integer addition. Computes self + rhs, returning None if \
* overflow occurred. \
*/ \
Expand All @@ -261,6 +261,19 @@
return Option<T>::none(); \
} \
\
/** Checked integer addition with an unsigned rhs. Computes self + rhs, \
* returning None if overflow occurred. \
*/ \
template <std::same_as<SignedT> S> \
constexpr Option<T> checked_add_signed(const S& rhs) const& noexcept { \
const auto out = __private::add_with_overflow_signed(primitive_value, \
rhs.primitive_value); \
if (!out.overflow) [[likely]] \
return Option<T>::some(out.value); \
else \
return Option<T>::none(); \
} \
\
/** Calculates self + rhs \
* \
* Returns a tuple of the addition along with a boolean indicating whether \
Expand All @@ -273,13 +286,46 @@
return Tuple<T, bool>::with(out.value, out.overflow); \
} \
\
/** Calculates self + rhs with an unsigned rhs \
* \
* Returns a tuple of the addition along with a boolean indicating whether \
* an arithmetic overflow would occur. If an overflow would have occurred \
* then the wrapped value is returned. \
*/ \
template <std::same_as<SignedT> S> \
constexpr Tuple<T, bool> overflowing_add_signed(const S& rhs) \
const& noexcept { \
const auto r = __private::add_with_overflow_signed(primitive_value, \
rhs.primitive_value); \
return Tuple<T, bool>::with(r.value, r.overflow); \
} \
\
/** Saturating integer addition. Computes self + rhs, saturating at the \
* numeric bounds instead of overflowing. \
*/ \
constexpr T saturating_add(const T& rhs) const& noexcept { \
return __private::saturating_add(primitive_value, rhs.primitive_value); \
} \
\
/** Saturating integer addition with an unsigned rhs. Computes self + rhs, \
* saturating at the numeric bounds instead of overflowing. \
*/ \
template <std::same_as<SignedT> S> \
constexpr T saturating_add_signed(const S& rhs) const& noexcept { \
const auto r = __private::add_with_overflow_signed(primitive_value, \
rhs.primitive_value); \
if (!r.overflow) [[likely]] \
return r.value; \
else { \
/* TODO: Can this be done without a branch? If it's complex or uses \
* compiler stuff, move into intrinsics. */ \
if (rhs.primitive_value >= 0) \
return MAX(); \
else \
return MIN(); \
} \
} \
\
/** Unchecked integer addition. Computes self + rhs, assuming overflow \
* cannot occur. \
* \
Expand All @@ -298,6 +344,16 @@
*/ \
constexpr T wrapping_add(const T& rhs) const& noexcept { \
return __private::wrapping_add(primitive_value, rhs.primitive_value); \
} \
\
/** Wrapping (modular) addition with an unsigned rhs. Computes self + rhs, \
* wrapping around at the boundary of the type. \
*/ \
template <std::same_as<SignedT> S> \
constexpr T wrapping_add_signed(const S& rhs) const& noexcept { \
return __private::add_with_overflow_signed(primitive_value, \
rhs.primitive_value) \
.value; \
} \
static_assert(true)

Expand Down
Loading