无符号整数的差异会导致生锈的有符号整数。

3
我知道Rust有混合整数操作,但我找不到一种直接的方法来获取两个无符号整数的有符号差,同时正确处理溢出情况。
// 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);

我可以尝试用转换来实现它,但是我不确定如何正确地检测溢出。

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));
        }
    }
}

如上所示的测试中,从概念上讲,它是uX::overflowing_add_signed(iX)方法的逆操作,并且在相同的情况下会溢出。

@cafce25 这样可以得到正确的数字结果,但标志位会是错误的。这告诉你是否溢出了usize,而不是isize。我已经编辑了问题,添加了一个简单的测试案例。 - undefined
4个回答

2
你还可以使用 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))
}

如果减法不溢出`usize`,结果是正数,并且当结果超过`isize::MAX`时,溢出`isize`,这相当于在转换为`isize`时为负数。
如果减法溢出`usize`,结果是负数,并且当结果低于`isize::MIN`时,溢出`isize`,这相当于在转换为`isize`时为正数。 关于`u8`/`i8`的正确性证明

1
这是一个非常简单且始终正确的版本,假设usize不超过64位(目前在所有支持的平台上都是真的)。
fn signed_overflowing_sub(self, rhs: Self) -> (Self::Signed, bool) {
    let result = i128::try_from(self).expect("i128 too small")
        - i128::try_from(rhs).expect("i128 too small");
    (result as isize, isize::try_from(result).is_err())
}

我甚至不确定它会比手动检查更低效,它可能更高效。

我忘了这个事实,我用它来确认我的测试是正确的。谢谢! - undefined

1
语义很难把握准确,但以下是我认为正确的版本:
fn signed_overflowing_sub(self, rhs: Self) -> (Self::Signed, bool) {
    if self < rhs {
        let result = rhs - self;
        if result == 1 << (usize::BITS - 1) /* -isize::MIN */ {
            // `-isize::MIN` will overflow if we convert to it `isize`, so we need to handle it specifically.
            (isize::MIN, false)
        } else {
            (-(result as isize), result > isize::MAX as usize)
        }
    } else {
        let result = self - rhs;
        (result as isize, result > isize::MAX as usize)
    }
}

1
通过检查相关操作的标准库实现的代码,我差不多偶然发现了一个相当简洁的辅助特性实现:
trait Cast {
    type OtherSign: Cast<OtherSign = Self>;
    fn overflowing_cast(self) -> (Self::OtherSign, bool);
}

impl Cast for isize {
    type OtherSign = usize;

    fn overflowing_cast(self) -> (Self::OtherSign, bool) {
        (0 as Self::OtherSign).overflowing_add_signed(self)
    }
}
impl Cast for usize {
    type OtherSign = isize;

    fn overflowing_cast(self) -> (Self::OtherSign, bool) {
        (0 as Self::OtherSign).overflowing_add_unsigned(self)
    }
}

impl SignedSub for usize {
    fn signed_overflowing_sub(self, rhs: Self) -> (Self::Signed, bool) {
        let (res, overflowed) = self.wrapping_sub(rhs).overflowing_cast();
        (res, overflowed ^ (self < rhs))
    }
    ...
}

这个通过了测试,但不足之处是我对它为什么有效不太清楚。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接