diff --git a/src/lib.rs b/src/lib.rs index 2d2b350..5eb93c8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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 { - ::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. @@ -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 { + ::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 @@ -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 @@ -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 { + ::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 ) } } diff --git a/src/tests.rs b/src/tests.rs index b48615f..5bdbd9b 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -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 @@ -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);