Skip to content

Commit 0108268

Browse files
committed
Add all unsigned addition with signed values
1 parent 6e3806e commit 0108268

File tree

4 files changed

+144
-23
lines changed

4 files changed

+144
-23
lines changed

num/__private/intrinsics.h

+14-1
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,18 @@ sus_always_inline
426426
};
427427
}
428428

429+
template <class T, class U = decltype(to_signed(std::declval<T>()))>
430+
requires(std::is_integral_v<T> && !std::is_signed_v<T> && sizeof(T) <= 8 &&
431+
sizeof(T) == sizeof(U))
432+
sus_always_inline
433+
constexpr OverflowOut<T> add_with_overflow_signed(T x, U y) noexcept {
434+
return OverflowOut<T>{
435+
.overflow = (y >= 0 && into_unsigned(y) > max_value<T>() - x) ||
436+
(y < 0 && into_unsigned(-y) > x),
437+
.value = x + into_unsigned(y),
438+
};
439+
}
440+
429441
template <class T, class U = decltype(to_unsigned(std::declval<T>()))>
430442
requires(std::is_integral_v<T> && std::is_signed_v<T> && sizeof(T) <= 8 &&
431443
sizeof(T) == sizeof(U))
@@ -478,7 +490,8 @@ sus_always_inline
478490
// TODO: Can we use compiler intrinsics?
479491
auto out = into_widened(x) * into_widened(y);
480492
using Wide = decltype(out);
481-
return OverflowOut{.overflow = out > Wide{max_value<T>()}, .value = static_cast<T>(out)};
493+
return OverflowOut{.overflow = out > Wide{max_value<T>()},
494+
.value = static_cast<T>(out)};
482495
}
483496

484497
template <class T>

num/__private/unsigned_integer_macros.h

+76-20
Original file line numberDiff line numberDiff line change
@@ -36,25 +36,25 @@
3636
} \
3737
static_assert(true)
3838

39-
#define _sus__unsigned_impl(T, Bytes, LargerT) \
40-
_sus__unsigned_from(T); \
41-
_sus__unsigned_integer_comparison(T); \
42-
_sus__unsigned_unary_ops(T); \
43-
_sus__unsigned_binary_logic_ops(T); \
44-
_sus__unsigned_binary_bit_ops(T); \
45-
_sus__unsigned_mutable_logic_ops(T); \
46-
_sus__unsigned_mutable_bit_ops(T); \
47-
_sus__unsigned_abs(T); \
48-
_sus__unsigned_add(T); \
49-
_sus__unsigned_div(T); \
50-
_sus__unsigned_mul(T, LargerT); \
51-
_sus__unsigned_neg(T); \
52-
_sus__unsigned_rem(T); \
53-
_sus__unsigned_shift(T); \
54-
_sus__unsigned_sub(T); \
55-
_sus__unsigned_bits(T); \
56-
_sus__unsigned_pow(T); \
57-
_sus__unsigned_log(T); \
39+
#define _sus__unsigned_impl(T, Bytes, SignedT, LargerT) \
40+
_sus__unsigned_from(T); \
41+
_sus__unsigned_integer_comparison(T); \
42+
_sus__unsigned_unary_ops(T); \
43+
_sus__unsigned_binary_logic_ops(T); \
44+
_sus__unsigned_binary_bit_ops(T); \
45+
_sus__unsigned_mutable_logic_ops(T); \
46+
_sus__unsigned_mutable_bit_ops(T); \
47+
_sus__unsigned_abs(T); \
48+
_sus__unsigned_add(T, SignedT); \
49+
_sus__unsigned_div(T); \
50+
_sus__unsigned_mul(T, LargerT); \
51+
_sus__unsigned_neg(T); \
52+
_sus__unsigned_rem(T); \
53+
_sus__unsigned_shift(T); \
54+
_sus__unsigned_sub(T); \
55+
_sus__unsigned_bits(T); \
56+
_sus__unsigned_pow(T); \
57+
_sus__unsigned_log(T); \
5858
_sus__unsigned_endian(T, Bytes)
5959

6060
#define _sus__unsigned_from(T) \
@@ -248,7 +248,7 @@
248248
} \
249249
static_assert(true)
250250

251-
#define _sus__unsigned_add(T) \
251+
#define _sus__unsigned_add(T, SignedT) \
252252
/** Checked integer addition. Computes self + rhs, returning None if \
253253
* overflow occurred. \
254254
*/ \
@@ -261,6 +261,19 @@
261261
return Option<T>::none(); \
262262
} \
263263
\
264+
/** Checked integer addition with an unsigned rhs. Computes self + rhs, \
265+
* returning None if overflow occurred. \
266+
*/ \
267+
template <std::same_as<SignedT> S> \
268+
constexpr Option<T> checked_add_signed(const S& rhs) const& noexcept { \
269+
const auto out = __private::add_with_overflow_signed(primitive_value, \
270+
rhs.primitive_value); \
271+
if (!out.overflow) [[likely]] \
272+
return Option<T>::some(out.value); \
273+
else \
274+
return Option<T>::none(); \
275+
} \
276+
\
264277
/** Calculates self + rhs \
265278
* \
266279
* Returns a tuple of the addition along with a boolean indicating whether \
@@ -273,13 +286,46 @@
273286
return Tuple<T, bool>::with(out.value, out.overflow); \
274287
} \
275288
\
289+
/** Calculates self + rhs with an unsigned rhs \
290+
* \
291+
* Returns a tuple of the addition along with a boolean indicating whether \
292+
* an arithmetic overflow would occur. If an overflow would have occurred \
293+
* then the wrapped value is returned. \
294+
*/ \
295+
template <std::same_as<SignedT> S> \
296+
constexpr Tuple<T, bool> overflowing_add_signed(const S& rhs) \
297+
const& noexcept { \
298+
const auto r = __private::add_with_overflow_signed(primitive_value, \
299+
rhs.primitive_value); \
300+
return Tuple<T, bool>::with(r.value, r.overflow); \
301+
} \
302+
\
276303
/** Saturating integer addition. Computes self + rhs, saturating at the \
277304
* numeric bounds instead of overflowing. \
278305
*/ \
279306
constexpr T saturating_add(const T& rhs) const& noexcept { \
280307
return __private::saturating_add(primitive_value, rhs.primitive_value); \
281308
} \
282309
\
310+
/** Saturating integer addition with an unsigned rhs. Computes self + rhs, \
311+
* saturating at the numeric bounds instead of overflowing. \
312+
*/ \
313+
template <std::same_as<SignedT> S> \
314+
constexpr T saturating_add_signed(const S& rhs) const& noexcept { \
315+
const auto r = __private::add_with_overflow_signed(primitive_value, \
316+
rhs.primitive_value); \
317+
if (!r.overflow) [[likely]] \
318+
return r.value; \
319+
else { \
320+
/* TODO: Can this be done without a branch? If it's complex or uses \
321+
* compiler stuff, move into intrinsics. */ \
322+
if (rhs.primitive_value >= 0) \
323+
return MAX(); \
324+
else \
325+
return MIN(); \
326+
} \
327+
} \
328+
\
283329
/** Unchecked integer addition. Computes self + rhs, assuming overflow \
284330
* cannot occur. \
285331
* \
@@ -298,6 +344,16 @@
298344
*/ \
299345
constexpr T wrapping_add(const T& rhs) const& noexcept { \
300346
return __private::wrapping_add(primitive_value, rhs.primitive_value); \
347+
} \
348+
\
349+
/** Wrapping (modular) addition with an unsigned rhs. Computes self + rhs, \
350+
* wrapping around at the boundary of the type. \
351+
*/ \
352+
template <std::same_as<SignedT> S> \
353+
constexpr T wrapping_add_signed(const S& rhs) const& noexcept { \
354+
return __private::add_with_overflow_signed(primitive_value, \
355+
rhs.primitive_value) \
356+
.value; \
301357
} \
302358
static_assert(true)
303359

num/u32.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@ struct u32 {
6363
// u32_defn.h and u32_impl.h, allowing most of the library to just use
6464
// u32_defn.h which will keep headers smaller.
6565
_sus__unsigned_constants(u32, 0xFFFFFFFF);
66-
_sus__unsigned_impl(u32, sizeof(primitive_type), /*LargerT=*/uint64_t);
66+
_sus__unsigned_impl(u32, sizeof(primitive_type), /*SignedT=*/i32,
67+
/*LargerT=*/uint64_t);
6768

6869
// TODO: overflowing_div_euclid().
6970
// TODO: overflowing_rem_euclid().

num/u32_unittest.cc

+52-1
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@
1818

1919
#include "concepts/into.h"
2020
#include "concepts/make_default.h"
21-
#include "num/i32.h"
2221
#include "mem/__private/relocate.h"
22+
#include "num/i32.h"
2323
#include "num/num_concepts.h"
2424
#include "option/option.h"
2525
#include "third_party/googletest/googletest/include/gtest/gtest.h"
@@ -1219,6 +1219,57 @@ TEST(u32, ToNeBytes) {
12191219
}
12201220
}
12211221

1222+
// ** Unsigned only.
1223+
TEST(u32, CheckedAddSigned) {
1224+
constexpr auto a = (1_u32).checked_add_signed(3_i32);
1225+
EXPECT_EQ(a, Option<u32>::some(4_u32));
1226+
1227+
EXPECT_EQ((1_u32).checked_add_signed(2_i32), Option<u32>::some(3_u32));
1228+
EXPECT_EQ((u32::MIN() + 1_u32).checked_add_signed(-1_i32),
1229+
Option<u32>::some(u32::MIN()));
1230+
EXPECT_EQ((u32::MIN()).checked_add_signed(-1_i32), None);
1231+
EXPECT_EQ((u32::MAX() - 2_u32).checked_add_signed(3_i32), None);
1232+
}
1233+
1234+
// ** Unsigned only.
1235+
TEST(u32, OverflowingAddSigned) {
1236+
constexpr auto a = (1_u32).overflowing_add_signed(3_i32);
1237+
EXPECT_EQ(a, (Tuple<u32, bool>::with(4_u32, false)));
1238+
1239+
EXPECT_EQ((1_u32).overflowing_add_signed(2_i32),
1240+
(Tuple<u32, bool>::with(3_u32, false)));
1241+
EXPECT_EQ((u32::MIN() + 1_u32).overflowing_add_signed(-1_i32),
1242+
(Tuple<u32, bool>::with(u32::MIN(), false)));
1243+
EXPECT_EQ((u32::MIN()).overflowing_add_signed(-1_i32),
1244+
(Tuple<u32, bool>::with(u32::MAX(), true)));
1245+
EXPECT_EQ((u32::MAX() - 2_u32).overflowing_add_signed(3_i32),
1246+
(Tuple<u32, bool>::with(u32::MIN(), true)));
1247+
}
1248+
1249+
// ** Unsigned only.
1250+
TEST(u32, SaturatingAddSigned) {
1251+
constexpr auto a = (1_u32).saturating_add_signed(3_i32);
1252+
EXPECT_EQ(a, 4_u32);
1253+
1254+
EXPECT_EQ((1_u32).saturating_add_signed(2_i32), 3_u32);
1255+
EXPECT_EQ((u32::MIN() + 1_u32).saturating_add_signed(-1_i32),
1256+
u32::MIN());
1257+
EXPECT_EQ((u32::MIN()).saturating_add_signed(-1_i32), u32::MIN());
1258+
EXPECT_EQ((u32::MAX() - 2_u32).saturating_add_signed(3_i32), u32::MAX());
1259+
}
1260+
1261+
// ** Unsigned only.
1262+
TEST(u32, WrappingAddSigned) {
1263+
constexpr auto a = (1_u32).wrapping_add_signed(3_i32);
1264+
EXPECT_EQ(a, 4_u32);
1265+
1266+
EXPECT_EQ((1_u32).wrapping_add_signed(2_i32), 3_u32);
1267+
EXPECT_EQ((u32::MIN() + 1_u32).wrapping_add_signed(-1_i32),
1268+
u32::MIN());
1269+
EXPECT_EQ((u32::MIN()).wrapping_add_signed(-1_i32), u32::MAX());
1270+
EXPECT_EQ((u32::MAX() - 2_u32).wrapping_add_signed(3_i32), u32::MIN());
1271+
}
1272+
12221273
TEST(u32, From) {
12231274
static_assert(sus::concepts::from::From<u32, i32>);
12241275

0 commit comments

Comments
 (0)