提问人:Zoey Hewll 提问时间:11/16/2023 最后编辑:Zoey Hewll 更新时间:11/16/2023 访问量:107
无符号整数的 Rust 有符号差值
rust signed difference of unsigned integers
问:
我知道 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)
答:
1赞
Chayim Friedman
11/16/2023
#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赞
Chayim Friedman
11/16/2023
#2
这是一个非常简单的始终正确的版本,它假设不超过 64 位(目前在所有支持的平台上都是如此):usize
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())
}
我什至不确定它的效率会低于手动检查,它还不如更有效率。
评论
0赞
Zoey Hewll
11/16/2023
我忘记了这个事实,我用它来仔细检查我的测试是否正确。谢谢!
1赞
Zoey Hewll
11/16/2023
#3
通过检查相关操作的 std lib 实现的代码,我偶然发现了一个相当小的实现,就帮助程序特征而言:
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))
}
...
}
这通过了测试,但缺点是我不太清楚它为什么有效。
2赞
Ry-
11/16/2023
#4
您还可以从以下位置构建它: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
isize::MAX
isize
如果减法确实溢出,则结果为负数,并且恰好在 下溢出,这相当于转换为 时为正数。
usize
isize
isize::MIN
isize
评论
usize
isize