Skip to content
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

feat: wrapping sub #13

Merged
merged 6 commits into from
Dec 2, 2023
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
107 changes: 86 additions & 21 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -737,26 +737,19 @@ macro_rules! impl_ranged {
Self::new_saturating(self.get().saturating_pow(exp))
}

/// Wrapping integer addition. Computes `self + rhs`, wrapping around the numeric
/// bounds.
/// Compute the rem_euclid of this type with its unsigned type equivalent
// Not public because it doesn't match stdlib's "method_unsigned implemented only for signed type" tradition.
// Also because this isn't implemented for normal types in std.
// TODO maybe make public anyway? It is useful.
#[must_use = "this returns the result of the operation, without modifying the original"]
#[inline]
#[allow(trivial_casts, trivial_numeric_casts)] // needed since some casts have to send unsigned -> unsigned to handle signed -> unsigned
pub const fn wrapping_add(self, rhs: $internal) -> Self {
<Self as $crate::traits::RangeIsValid>::ASSERT;
// Forward to internal type's impl if same as type.
if MIN == $internal::MIN && MAX == $internal::MAX {
// Safety: std's wrapping methods match ranged arithmetic when the range is the internal datatype's range.
return unsafe { Self::new_unchecked(self.get().wrapping_add(rhs)) }
}

let inner = self.get();
// Won't overflow because of std impl forwarding.
let range_len = MAX.abs_diff(MIN) + 1;
// Calculate the offset with proper handling for negative rhs
#[allow(trivial_numeric_casts)] // needed since some casts have to send unsigned -> unsigned to handle signed -> unsigned
const fn rem_euclid_unsigned(
rhs: $internal,
range_len: $unsigned_type
) -> $unsigned_type {
#[allow(unused_comparisons)]
// equivalent to `rem_euclid_unsigned()` if that method existed
let offset = if rhs >= 0 {
if rhs >= 0 {
(rhs as $unsigned_type) % range_len
} else {
// Let ux refer to an n bit unsigned and ix refer to an n bit signed integer.
Expand All @@ -770,7 +763,29 @@ macro_rules! impl_ranged {
// ux::MAX / 2 + 1 = 2^(n-1) so this subtraction will always be a >= 0 after subtraction
// Thus converting rhs signed negative to equivalent positive value in mod range_len arithmetic
((($unsigned_type::MAX / range_len) * range_len) - (rhs_abs)) % range_len
};
}
}

/// Wrapping integer addition. Computes `self + rhs`, wrapping around the numeric
/// bounds.
#[must_use = "this returns the result of the operation, without modifying the original"]
#[inline]
#[allow(trivial_numeric_casts)] // needed since some casts have to send unsigned -> unsigned to handle signed -> unsigned
pub const fn wrapping_add(self, rhs: $internal) -> Self {
<Self as $crate::traits::RangeIsValid>::ASSERT;
// Forward to internal type's impl if same as type.
if MIN == $internal::MIN && MAX == $internal::MAX {
// Safety: std's wrapping methods match ranged arithmetic when the range is the internal datatype's range.
return unsafe { Self::new_unchecked(self.get().wrapping_add(rhs)) }
}

let inner = self.get();

// Won't overflow because of std impl forwarding.
let range_len = MAX.abs_diff(MIN) + 1;

// Calculate the offset with proper handling for negative rhs
let offset = Self::rem_euclid_unsigned(rhs, range_len);

let greater_vals = MAX.abs_diff(inner);
// No wrap
Expand All @@ -780,7 +795,7 @@ macro_rules! impl_ranged {
// if inner < 0: Same as >=0 with caveat:
// `(signed as unsigned).wrapping_add(unsigned) as signed` is the same as
// `signed::checked_add_unsigned(unsigned).unwrap()` or `wrapping_add_unsigned`
// (the difference doesn't matter since it won't overflow the signed type),
// (the difference doesn't matter since it won't overflow),
// but unsigned integers don't have either method so it won't compile that way.
unsafe { Self::new_unchecked(
((inner as $unsigned_type).wrapping_add(offset)) as $internal
Expand All @@ -790,12 +805,62 @@ macro_rules! impl_ranged {
else {
// Safety:
// - offset < range_len by rem_euclid (MIN + ... safe)
// - offset > greater_values from if statement (offset - (greater_values + 1) safe)
// - offset > greater_vals from if statement (offset - (greater_vals + 1) safe)
//
// again using `(signed as unsigned).wrapping_add(unsigned) as signed` = `checked_add_unsigned` trick
unsafe { Self::new_unchecked(
((MIN as $unsigned_type).wrapping_add(
offset - ((greater_vals + 1))
offset - (greater_vals + 1)
)) as $internal
) }
}
}

/// Wrapping integer subtraction. Computes `self - rhs`, wrapping around the numeric
/// bounds.
#[must_use = "this returns the result of the operation, without modifying the original"]
#[inline]
#[allow(trivial_numeric_casts)] // needed since some casts have to send unsigned -> unsigned to handle signed -> unsigned
pub const fn wrapping_sub(self, rhs: $internal) -> Self {
<Self as $crate::traits::RangeIsValid>::ASSERT;
// Forward to internal type's impl if same as type.
if MIN == $internal::MIN && MAX == $internal::MAX {
// Safety: std's wrapping methods match ranged arithmetic when the range is the internal datatype's range.
return unsafe { Self::new_unchecked(self.get().wrapping_sub(rhs)) }
}

let inner = self.get();

// Won't overflow because of std impl forwarding.
let range_len = MAX.abs_diff(MIN) + 1;

// Calculate the offset with proper handling for negative rhs
let offset = Self::rem_euclid_unsigned(rhs, range_len);

let lesser_vals = MIN.abs_diff(inner);
// No wrap
if offset <= lesser_vals {
// Safety:
// if inner >= 0 -> No overflow beyond range (offset <= greater_vals)
// if inner < 0: Same as >=0 with caveat:
// `(signed as unsigned).wrapping_sub(unsigned) as signed` is the same as
// `signed::checked_sub_unsigned(unsigned).unwrap()` or `wrapping_sub_unsigned`
// (the difference doesn't matter since it won't overflow below 0),
// but unsigned integers don't have either method so it won't compile that way.
unsafe { Self::new_unchecked(
((inner as $unsigned_type).wrapping_sub(offset)) as $internal
) }
}
// Wrap
else {
// Safety:
// - offset < range_len by rem_euclid (MAX - ... safe)
// - offset > lesser_vals from if statement (offset - (lesser_vals + 1) safe)
//
// again using `(signed as unsigned).wrapping_sub(unsigned) as signed` = `checked_sub_unsigned` trick
unsafe { Self::new_unchecked(
((MAX as $unsigned_type).wrapping_sub(
offset - (lesser_vals + 1)
)) as $internal
) }
}
Expand Down
38 changes: 36 additions & 2 deletions src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -393,8 +393,10 @@ macro_rules! tests {
assert_eq!($t::<{ $inner::MIN }, { $inner::MAX }>::MAX.wrapping_add(1),
$t::<{ $inner::MIN }, { $inner::MAX }>::MIN);
for i in 1..127 {
assert_eq!($t::<{ $inner::MIN}, { $inner::MAX - 1 }>::MAX.wrapping_add(i),
$t::<{ $inner::MIN}, { $inner::MAX - 1 }>::new($inner::MIN + i - 1).unwrap_or_else(|| panic!("adding {i}+{} does not yield {}", $inner::MIN, $inner::MAX + i )));
assert_eq!(
$t::<{ $inner::MIN}, { $inner::MAX - 1 }>::MAX.wrapping_add(i),
$t::<{ $inner::MIN}, { $inner::MAX - 1 }>::new($inner::MIN + i - 1).unwrap_or_else(|| panic!("adding {i}+{} does not yield {}", $inner::MIN, $inner::MAX + i ))
);
}
)*
$(if_signed! { $signed
Expand Down Expand Up @@ -423,6 +425,38 @@ macro_rules! tests {
})*
}

#[test]
fn wrapping_sub() {
$(
assert_eq!($t::<5, 10>::MIN.wrapping_sub(0), $t::<5, 10>::MIN);
assert_eq!($t::<5, 10>::MIN.wrapping_sub(1), $t::<5, 10>::MAX);
assert_eq!($t::<5, 10>::new(5 + 1).unwrap().wrapping_sub(1), $t::<5, 10>::MIN);
assert_eq!($t::<5, 10>::MAX.wrapping_sub(1), $t::<5, 10>::new(10 - 1).unwrap());
assert_eq!($t::<{ $inner::MIN }, { $inner::MAX }>::MIN.wrapping_sub(1),
$t::<{ $inner::MIN }, { $inner::MAX }>::MAX);
for i in 1..127 {
assert_eq!(
$t::<{ $inner::MIN + 1 }, { $inner::MAX }>::MIN.wrapping_sub(i),
$t::<{ $inner::MIN + 1 }, { $inner::MAX }>::new($inner::MAX - i + 1).unwrap_or_else(|| panic!("failed test at iteration {i}"))
);
}
)*
$(if_signed! { $signed
for i in -127..=127 {
assert_eq!($t::<-5, 126>::MIN.wrapping_add(i), $t::<-5,126>::MIN.wrapping_sub(-i), "failed test at {i}");
assert_eq!($t::<-5, 126>::MIN.wrapping_add(-i), $t::<-5,126>::MIN.wrapping_sub(i), "failed test at {i}");
}
assert_eq!(
$t::<-5, 126>::MIN.wrapping_add(127).wrapping_add(1),
$t::<-5,126>::MIN.wrapping_sub(-128)
);
assert_eq!(
$t::<-5, 126>::MIN.wrapping_add(-128),
$t::<-5,126>::MIN.wrapping_sub(127).wrapping_sub(1)
);
})*
}

#[test]
fn saturating_sub() {$(
assert_eq!($t::<5, 10>::MIN.saturating_sub(0), $t::<5, 10>::MIN);
Expand Down