diff --git a/num/__private/intrinsics.h b/num/__private/intrinsics.h index ff489da0c..e4d3e861a 100644 --- a/num/__private/intrinsics.h +++ b/num/__private/intrinsics.h @@ -426,6 +426,18 @@ sus_always_inline }; } +template ()))> + requires(std::is_integral_v && !std::is_signed_v && sizeof(T) <= 8 && + sizeof(T) == sizeof(U)) +sus_always_inline + constexpr OverflowOut add_with_overflow_signed(T x, U y) noexcept { + return OverflowOut{ + .overflow = (y >= 0 && into_unsigned(y) > max_value() - x) || + (y < 0 && into_unsigned(-y) > x), + .value = x + into_unsigned(y), + }; +} + template ()))> requires(std::is_integral_v && std::is_signed_v && sizeof(T) <= 8 && sizeof(T) == sizeof(U)) @@ -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()}, .value = static_cast(out)}; + return OverflowOut{.overflow = out > Wide{max_value()}, + .value = static_cast(out)}; } template diff --git a/num/__private/signed_integer_macros.h b/num/__private/signed_integer_macros.h index afbe9d706..d09bbcb54 100644 --- a/num/__private/signed_integer_macros.h +++ b/num/__private/signed_integer_macros.h @@ -390,6 +390,19 @@ return Option::none(); \ } \ \ + /** Checked integer addition with an unsigned rhs. Computes self + rhs, \ + * returning None if overflow occurred. \ + */ \ + constexpr Option 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::some(out.value); \ + else \ + return Option::none(); \ + } \ + \ /** Calculates self + rhs \ * \ * Returns a tuple of the addition along with a boolean indicating whether \ @@ -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. \ * \ @@ -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) @@ -773,6 +805,17 @@ return Option::none(); \ } \ \ + /** Checked integer subtraction with an unsigned rhs. Computes self - rhs, \ + * returning None if overflow occurred. \ + */ \ + constexpr Option checked_sub_unsigned(const UnsignedT& rhs) const& { \ + auto out = __private::sub_with_overflow_unsigned(primitive_value, rhs); \ + if (!out.overflow) [[likely]] \ + return Option::some(out.value); \ + else \ + return Option::none(); \ + } \ + \ /** Calculates self - rhs \ * \ * Returns a tuple of the subtraction along with a boolean indicating \ @@ -785,7 +828,7 @@ return Tuple::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 \ @@ -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. \ */ \ @@ -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) diff --git a/num/__private/unsigned_integer_macros.h b/num/__private/unsigned_integer_macros.h index 93b70c591..886d18e4c 100644 --- a/num/__private/unsigned_integer_macros.h +++ b/num/__private/unsigned_integer_macros.h @@ -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) \ @@ -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. \ */ \ @@ -261,6 +261,19 @@ return Option::none(); \ } \ \ + /** Checked integer addition with an unsigned rhs. Computes self + rhs, \ + * returning None if overflow occurred. \ + */ \ + template S> \ + constexpr Option 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::some(out.value); \ + else \ + return Option::none(); \ + } \ + \ /** Calculates self + rhs \ * \ * Returns a tuple of the addition along with a boolean indicating whether \ @@ -273,6 +286,20 @@ return Tuple::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 S> \ + constexpr Tuple overflowing_add_signed(const S& rhs) \ + const& noexcept { \ + const auto r = __private::add_with_overflow_signed(primitive_value, \ + rhs.primitive_value); \ + return Tuple::with(r.value, r.overflow); \ + } \ + \ /** Saturating integer addition. Computes self + rhs, saturating at the \ * numeric bounds instead of overflowing. \ */ \ @@ -280,6 +307,25 @@ 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 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. \ * \ @@ -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 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) diff --git a/num/i32_unittest.cc b/num/i32_unittest.cc index 14b610406..86b530177 100644 --- a/num/i32_unittest.cc +++ b/num/i32_unittest.cc @@ -413,26 +413,6 @@ TEST(i32, OverflowingAdd) { (Tuple::with(0_i32, true))); } -// ** Signed only. -TEST(i32, OverflowingAddUnsigned) { - constexpr auto a = (1_i32).overflowing_add_unsigned(3u); - EXPECT_EQ(a, (Tuple::with(4_i32, false))); - - EXPECT_EQ((-1_i32).overflowing_add_unsigned(2u), - (Tuple::with(1_i32, false))); - EXPECT_EQ( - (i32::MIN()).overflowing_add_unsigned(/* TODO: u32::MAX() */ UINT_MAX), - (Tuple::with(i32::MAX(), false))); - EXPECT_EQ((i32::MIN() + 1_i32) - .overflowing_add_unsigned(/* TODO: u32::MAX() */ UINT_MAX), - (Tuple::with(i32::MIN(), true))); - EXPECT_EQ((i32::MIN() + 1_i32) - .overflowing_add_unsigned(/* TODO: u32::MAX() */ UINT_MAX - 1), - (Tuple::with(i32::MAX(), false))); - EXPECT_EQ((i32::MAX() - 2_i32).overflowing_add_unsigned(3u), - (Tuple::with(i32::MIN(), true))); -} - TEST(i32, SaturatingAdd) { constexpr auto a = (1_i32).saturating_add(3_i32); EXPECT_EQ(a, 4_i32); @@ -1236,25 +1216,6 @@ TEST(i32, OverflowingSub) { (Tuple::with(i32::MIN(), true))); } -// ** Signed only. -TEST(i32, OverflowingSubUnsigned) { - [[maybe_unused]] constexpr auto a = (-1_i32).overflowing_sub_unsigned(3u); - - EXPECT_EQ((1_i32).overflowing_sub_unsigned(2u), - (Tuple::with(-1_i32, false))); - EXPECT_EQ( - (i32::MAX()).overflowing_sub_unsigned(/* TODO: u32::MAX() */ UINT_MAX), - (Tuple::with(i32::MIN(), false))); - EXPECT_EQ((i32::MAX() - 1_i32) - .overflowing_sub_unsigned(/* TODO: u32::MAX() */ UINT_MAX), - (Tuple::with(i32::MAX(), true))); - EXPECT_EQ((i32::MAX() - 1_i32) - .overflowing_sub_unsigned(/* TODO: u32::MAX() */ UINT_MAX - 1), - (Tuple::with(i32::MIN(), false))); - EXPECT_EQ((i32::MIN() + 2_i32).overflowing_sub_unsigned(3u), - (Tuple::with(i32::MAX(), true))); -} - TEST(i32, SaturatingSub) { constexpr auto a = (5_i32).saturating_sub(3_i32); EXPECT_EQ(a, 2_i32); @@ -1899,6 +1860,150 @@ TEST(i32, ToNeBytes) { } } +// ** Signed only. +TEST(i32, CheckedAddUnsigned) { + constexpr auto a = (1_i32).checked_add_unsigned(3u); + EXPECT_EQ(a, Option::some(4_i32)); + + EXPECT_EQ((-1_i32).checked_add_unsigned(2u), Option::some(1_i32)); + EXPECT_EQ((i32::MIN()).checked_add_unsigned(/* TODO: u32::MAX() */ UINT_MAX), + Option::some(i32::MAX())); + EXPECT_EQ((i32::MIN() + 1_i32) + .checked_add_unsigned(/* TODO: u32::MAX() */ UINT_MAX), + None); + EXPECT_EQ((i32::MIN() + 1_i32) + .checked_add_unsigned(/* TODO: u32::MAX() */ UINT_MAX - 1), + Option::some(i32::MAX())); + EXPECT_EQ((i32::MAX() - 2_i32).checked_add_unsigned(3u), None); +} + +// ** Signed only. +TEST(i32, OverflowingAddUnsigned) { + constexpr auto a = (1_i32).overflowing_add_unsigned(3u); + EXPECT_EQ(a, (Tuple::with(4_i32, false))); + + EXPECT_EQ((-1_i32).overflowing_add_unsigned(2u), + (Tuple::with(1_i32, false))); + EXPECT_EQ( + (i32::MIN()).overflowing_add_unsigned(/* TODO: u32::MAX() */ UINT_MAX), + (Tuple::with(i32::MAX(), false))); + EXPECT_EQ((i32::MIN() + 1_i32) + .overflowing_add_unsigned(/* TODO: u32::MAX() */ UINT_MAX), + (Tuple::with(i32::MIN(), true))); + EXPECT_EQ((i32::MIN() + 1_i32) + .overflowing_add_unsigned(/* TODO: u32::MAX() */ UINT_MAX - 1), + (Tuple::with(i32::MAX(), false))); + EXPECT_EQ((i32::MAX() - 2_i32).overflowing_add_unsigned(3u), + (Tuple::with(i32::MIN(), true))); +} + +// ** Signed only. +TEST(i32, SaturatingAddUnsigned) { + constexpr auto a = (1_i32).saturating_add_unsigned(3u); + EXPECT_EQ(a, 4_i32); + + EXPECT_EQ((-1_i32).saturating_add_unsigned(2u), 1_i32); + EXPECT_EQ( + (i32::MIN()).saturating_add_unsigned(/* TODO: u32::MAX() */ UINT_MAX), + i32::MAX()); + EXPECT_EQ((i32::MIN() + 1_i32) + .saturating_add_unsigned(/* TODO: u32::MAX() */ UINT_MAX), + i32::MAX()); + EXPECT_EQ((i32::MIN() + 1_i32) + .saturating_add_unsigned(/* TODO: u32::MAX() */ UINT_MAX - 1), + i32::MAX()); + EXPECT_EQ((i32::MAX() - 2_i32).saturating_add_unsigned(3u), i32::MAX()); +} + +// ** Signed only. +TEST(i32, WrappingAddUnsigned) { + constexpr auto a = (1_i32).wrapping_add_unsigned(3u); + EXPECT_EQ(a, 4_i32); + + EXPECT_EQ((-1_i32).wrapping_add_unsigned(2u), 1_i32); + EXPECT_EQ((i32::MIN()).wrapping_add_unsigned(/* TODO: u32::MAX() */ UINT_MAX), + i32::MAX()); + EXPECT_EQ((i32::MIN() + 1_i32) + .wrapping_add_unsigned(/* TODO: u32::MAX() */ UINT_MAX), + i32::MIN()); + EXPECT_EQ((i32::MIN() + 1_i32) + .wrapping_add_unsigned(/* TODO: u32::MAX() */ UINT_MAX - 1), + i32::MAX()); + EXPECT_EQ((i32::MAX() - 2_i32).wrapping_add_unsigned(3u), i32::MIN()); +} + +// ** Signed only. +TEST(i32, CheckedSubUnsigned) { + constexpr auto a = (1_i32).checked_sub_unsigned(3u); + EXPECT_EQ(a, Option::some(-2_i32)); + + EXPECT_EQ((-1_i32).checked_sub_unsigned(2u), Option::some(-3_i32)); + EXPECT_EQ((i32::MAX()).checked_sub_unsigned(/* TODO: u32::MAX() */ UINT_MAX), + Option::some(i32::MIN())); + EXPECT_EQ((i32::MAX() - 1_i32) + .checked_sub_unsigned(/* TODO: u32::MAX() */ UINT_MAX), + None); + EXPECT_EQ((i32::MAX() - 1_i32) + .checked_sub_unsigned(/* TODO: u32::MAX() */ UINT_MAX - 1), + Option::some(i32::MIN())); + EXPECT_EQ((i32::MIN() + 2_i32).checked_sub_unsigned(3u), None); +} + +// ** Signed only. +TEST(i32, OverflowingSubUnsigned) { + constexpr auto a = (1_i32).overflowing_sub_unsigned(3u); + EXPECT_EQ(a, (Tuple::with(-2_i32, false))); + + EXPECT_EQ((-1_i32).overflowing_sub_unsigned(2u), + (Tuple::with(-3_i32, false))); + EXPECT_EQ( + (i32::MAX()).overflowing_sub_unsigned(/* TODO: u32::MAX() */ UINT_MAX), + (Tuple::with(i32::MIN(), false))); + EXPECT_EQ((i32::MAX() - 1_i32) + .overflowing_sub_unsigned(/* TODO: u32::MAX() */ UINT_MAX), + (Tuple::with(i32::MAX(), true))); + EXPECT_EQ((i32::MAX() - 1_i32) + .overflowing_sub_unsigned(/* TODO: u32::MAX() */ UINT_MAX - 1), + (Tuple::with(i32::MIN(), false))); + EXPECT_EQ((i32::MIN() + 2_i32).overflowing_sub_unsigned(3u), + (Tuple::with(i32::MAX(), true))); +} + +// ** Signed only. +TEST(i32, SaturatingSubUnsigned) { + constexpr auto a = (1_i32).saturating_sub_unsigned(3u); + EXPECT_EQ(a, -2_i32); + + EXPECT_EQ((-1_i32).saturating_sub_unsigned(2u), -3_i32); + EXPECT_EQ( + (i32::MAX()).saturating_sub_unsigned(/* TODO: u32::MAX() */ UINT_MAX), + i32::MIN()); + EXPECT_EQ((i32::MAX() - 1_i32) + .saturating_sub_unsigned(/* TODO: u32::MAX() */ UINT_MAX), + i32::MIN()); + EXPECT_EQ((i32::MAX() - 1_i32) + .saturating_sub_unsigned(/* TODO: u32::MAX() */ UINT_MAX - 1), + i32::MIN()); + EXPECT_EQ((i32::MIN() + 2_i32).saturating_sub_unsigned(3u), i32::MIN()); +} + +// ** Signed only. +TEST(i32, WrappingSubUnsigned) { + constexpr auto a = (1_i32).wrapping_sub_unsigned(3u); + EXPECT_EQ(a, -2_i32); + + EXPECT_EQ((-1_i32).wrapping_sub_unsigned(2u), -3_i32); + EXPECT_EQ((i32::MAX()).wrapping_sub_unsigned(/* TODO: u32::MAX() */ UINT_MAX), + i32::MIN()); + EXPECT_EQ((i32::MAX() - 1_i32) + .wrapping_sub_unsigned(/* TODO: u32::MAX() */ UINT_MAX), + i32::MAX()); + EXPECT_EQ((i32::MAX() - 1_i32) + .wrapping_sub_unsigned(/* TODO: u32::MAX() */ UINT_MAX - 1), + i32::MIN()); + EXPECT_EQ((i32::MIN() + 2_i32).wrapping_sub_unsigned(3u), i32::MAX()); +} + TEST(i32, From) { static_assert(sus::concepts::from::From); diff --git a/num/u32.h b/num/u32.h index 327c60389..1c5c98e7a 100644 --- a/num/u32.h +++ b/num/u32.h @@ -63,7 +63,8 @@ struct u32 { // u32_defn.h and u32_impl.h, allowing most of the library to just use // u32_defn.h which will keep headers smaller. _sus__unsigned_constants(u32, 0xFFFFFFFF); - _sus__unsigned_impl(u32, sizeof(primitive_type), /*LargerT=*/uint64_t); + _sus__unsigned_impl(u32, sizeof(primitive_type), /*SignedT=*/i32, + /*LargerT=*/uint64_t); // TODO: overflowing_div_euclid(). // TODO: overflowing_rem_euclid(). @@ -76,7 +77,6 @@ struct u32 { // TODO: checked_next_multiple_of(). // TODO: from_str_radix(). Need Result type and Errors. // TODO: next_power_of_two(). - // TODO: checked_add_signed() and friends? /// The inner primitive value, in case it needs to be unwrapped from the /// type. Avoid using this member except to convert when a consumer diff --git a/num/u32_unittest.cc b/num/u32_unittest.cc index 5f3cb72ee..f55801bb8 100644 --- a/num/u32_unittest.cc +++ b/num/u32_unittest.cc @@ -18,8 +18,8 @@ #include "concepts/into.h" #include "concepts/make_default.h" -#include "num/i32.h" #include "mem/__private/relocate.h" +#include "num/i32.h" #include "num/num_concepts.h" #include "option/option.h" #include "third_party/googletest/googletest/include/gtest/gtest.h" @@ -1219,6 +1219,57 @@ TEST(u32, ToNeBytes) { } } +// ** Unsigned only. +TEST(u32, CheckedAddSigned) { + constexpr auto a = (1_u32).checked_add_signed(3_i32); + EXPECT_EQ(a, Option::some(4_u32)); + + EXPECT_EQ((1_u32).checked_add_signed(2_i32), Option::some(3_u32)); + EXPECT_EQ((u32::MIN() + 1_u32).checked_add_signed(-1_i32), + Option::some(u32::MIN())); + EXPECT_EQ((u32::MIN()).checked_add_signed(-1_i32), None); + EXPECT_EQ((u32::MAX() - 2_u32).checked_add_signed(3_i32), None); +} + +// ** Unsigned only. +TEST(u32, OverflowingAddSigned) { + constexpr auto a = (1_u32).overflowing_add_signed(3_i32); + EXPECT_EQ(a, (Tuple::with(4_u32, false))); + + EXPECT_EQ((1_u32).overflowing_add_signed(2_i32), + (Tuple::with(3_u32, false))); + EXPECT_EQ((u32::MIN() + 1_u32).overflowing_add_signed(-1_i32), + (Tuple::with(u32::MIN(), false))); + EXPECT_EQ((u32::MIN()).overflowing_add_signed(-1_i32), + (Tuple::with(u32::MAX(), true))); + EXPECT_EQ((u32::MAX() - 2_u32).overflowing_add_signed(3_i32), + (Tuple::with(u32::MIN(), true))); +} + +// ** Unsigned only. +TEST(u32, SaturatingAddSigned) { + constexpr auto a = (1_u32).saturating_add_signed(3_i32); + EXPECT_EQ(a, 4_u32); + + EXPECT_EQ((1_u32).saturating_add_signed(2_i32), 3_u32); + EXPECT_EQ((u32::MIN() + 1_u32).saturating_add_signed(-1_i32), + u32::MIN()); + EXPECT_EQ((u32::MIN()).saturating_add_signed(-1_i32), u32::MIN()); + EXPECT_EQ((u32::MAX() - 2_u32).saturating_add_signed(3_i32), u32::MAX()); +} + +// ** Unsigned only. +TEST(u32, WrappingAddSigned) { + constexpr auto a = (1_u32).wrapping_add_signed(3_i32); + EXPECT_EQ(a, 4_u32); + + EXPECT_EQ((1_u32).wrapping_add_signed(2_i32), 3_u32); + EXPECT_EQ((u32::MIN() + 1_u32).wrapping_add_signed(-1_i32), + u32::MIN()); + EXPECT_EQ((u32::MIN()).wrapping_add_signed(-1_i32), u32::MAX()); + EXPECT_EQ((u32::MAX() - 2_u32).wrapping_add_signed(3_i32), u32::MIN()); +} + TEST(u32, From) { static_assert(sus::concepts::from::From);