I know that rust has mixed integer operations, but I can't find a straightforward way to get a signed difference of two unsigned integers, while correctly handling overflow:
// one or both values may be too large to just cast to isize
let x: usize = (isize::MAX as usize) + 5;
let y: usize = (isize::MAX as usize) + 7;
let d: isize = x.signed_saturating_sub(y); // non-existent method
assert_eq!(d, -2);
I could try to implement it in terms of casts, but I'm not sure how to correctly detect overflow:
trait SignedSub {
type Signed;
fn signed_overflowing_sub(self, rhs: Self) -> (Self::Signed, bool);
fn signed_wrapping_sub(self, rhs: Self) -> Self::Signed;
fn signed_saturating_sub(self, rhs: Self) -> Self::Signed;
fn signed_checked_sub(self, rhs: Self) -> Option<Self::Signed>;
}
impl SignedSub for usize {
type Signed = isize;
fn signed_overflowing_sub(self, rhs: Self) -> (Self::Signed, bool) {
let (abs, neg) = if self < rhs {
(rhs - self, true)
} else {
(self - rhs, false)
};
let abs = abs as isize;
let res = match neg {
true => -abs,
false => abs,
};
(res, todo!("how to tell if it overflowed isize?"))
}
fn signed_wrapping_sub(self, rhs: Self) -> Self::Signed {
self.signed_overflowing_sub(rhs).0
}
fn signed_saturating_sub(self, rhs: Self) -> Self::Signed {
let (r, overflowed) = self.signed_overflowing_sub(rhs);
match overflowed {
true => match self.cmp(&rhs) {
Ordering::Less => isize::MIN,
Ordering::Equal => unreachable!(),
Ordering::Greater => isize::MAX,
},
false => r,
}
}
fn signed_checked_sub(self, rhs: Self) -> Option<Self::Signed> {
let (r, overflowed) = self.signed_overflowing_sub(rhs);
match overflowed {
true => None,
false => Some(r),
}
}
}
#[cfg(test)]
mod test {
use super::SignedSub;
use proptest::prelude::*;
proptest! {
#[test]
fn it_works(a: usize, b: isize) {
// a + b = c
// a + b - a = c - a
// b = c - a
let (c,flag) = a.overflowing_add_signed(b);
let (b2, flag2) = c.signed_overflowing_sub(a);
// the flags should be the same
assert_eq!((b, flag), (b2, flag2));
}
}
}
as shown in the test above, it's conceptually an inverse of the uX::overflowing_add_signed(iX)
method, and it overflows in the same circumstances.
You can also build this out of usize::overflowing_sub
:
fn signed_overflowing_sub(self, rhs: Self) -> (Self::Signed, bool) {
let (res, overflowed) = self.overflowing_sub(rhs);
let res = res as Self::Signed;
(res, overflowed ^ (res < 0))
}
If the subtraction doesn’t overflow usize
, the result is positive, and overflows isize
exactly when it’s over isize::MAX
, which is equivalent to being negative when cast to isize
.
If the subtraction does overflow usize
, the result is negative, and overflows isize
exactly when it’s under isize::MIN
, which is equivalent to being positive when cast to isize
.