Rust 溢出时带边界的整数运算
Integer operation with boundary when overflow in Rust
最近遇到的问题,需要根据整数类型的位进行带边界的整数运算
例如用i32
整数做add
运算,这里有一段伪代码来表达思路:
sum = a + b
max(min(sum, 2147483647), -2147483648)
// if the sum is larger than 2147483647, then return 2147483647.
// if the sum is smaller than -2147483648, then return -2147483648.
为了实现这一点,我天真地写了下面丑陋的代码:
fn i32_add_handling_by_casting(a: i32, b: i32) -> i32 {
let sum: i32;
if (a as i64 + b as i64) > 2147483647 as i64 {
sum = 2147483647;
} else if (a as i64 + b as i64) < -2147483648 as i64 {
sum = -2147483648;
} else {
sum = a + b;
}
sum
}
fn main() {
println!("{:?}", i32_add_handling_by_casting(2147483647, 1));
println!("{:?}", i32_add_handling_by_casting(-2147483648, -1));
}
代码运行良好;但是我的第六感告诉我,使用 type casting 是有问题的。因此,我尝试使用传统的恐慌(异常)处理来处理这个......但我坚持使用以下代码(恐慌结果无法检测到下溢或溢出):
use std::panic;
fn i32_add_handling_by_panic(a: i32, b: i32) -> i32 {
let sum: i32;
let result = panic::catch_unwind(|| {a + b}).ok();
match result {
Some(result) => { sum = result },
None => { sum = ? }
}
sum
}
fn main() {
println!("{:?}", i32_add_handling_by_panic(2147483647, 1));
println!("{:?}", i32_add_handling_by_panic(-2147483648, -1));
}
总结起来,我有3个问题:
- 我的类型转换解决方案对强类型语言有效吗? (如果可能的话,我需要解释为什么它有效或无效。)
- 有没有其他更好的方法来处理这个问题?
- panic可以分别处理不同的异常吗?
在这种情况下,Rust 标准库有一个名为 saturating_add
的方法,它支持您的用例:
assert_eq!(10_i32.saturating_add(20), 30);
assert_eq!(i32::MIN.saturating_add(-1), i32::MIN);
assert_eq!(i32::MAX.saturating_add(1), i32::MAX);
在内部,它被实现为 compiler intrinsic。
一般来说,此类问题不打算通过 panics 和 unwinding 来解决,它们仅用于在特殊情况下进行清理。 hand-written 版本可能涉及类型转换,但只计算一次 a as i64 + b as i64
。或者,这里有一个使用 checked_add
的版本,它 returns None
而不是溢出时的恐慌:
fn saturating_add(a: i32, b: i32) -> i32 {
if let Some(sum) = a.checked_add(b) {
sum
} else if a < 0 {
i32::MIN
} else {
i32::MAX
}
}
最近遇到的问题,需要根据整数类型的位进行带边界的整数运算
例如用i32
整数做add
运算,这里有一段伪代码来表达思路:
sum = a + b
max(min(sum, 2147483647), -2147483648)
// if the sum is larger than 2147483647, then return 2147483647.
// if the sum is smaller than -2147483648, then return -2147483648.
为了实现这一点,我天真地写了下面丑陋的代码:
fn i32_add_handling_by_casting(a: i32, b: i32) -> i32 {
let sum: i32;
if (a as i64 + b as i64) > 2147483647 as i64 {
sum = 2147483647;
} else if (a as i64 + b as i64) < -2147483648 as i64 {
sum = -2147483648;
} else {
sum = a + b;
}
sum
}
fn main() {
println!("{:?}", i32_add_handling_by_casting(2147483647, 1));
println!("{:?}", i32_add_handling_by_casting(-2147483648, -1));
}
代码运行良好;但是我的第六感告诉我,使用 type casting 是有问题的。因此,我尝试使用传统的恐慌(异常)处理来处理这个......但我坚持使用以下代码(恐慌结果无法检测到下溢或溢出):
use std::panic;
fn i32_add_handling_by_panic(a: i32, b: i32) -> i32 {
let sum: i32;
let result = panic::catch_unwind(|| {a + b}).ok();
match result {
Some(result) => { sum = result },
None => { sum = ? }
}
sum
}
fn main() {
println!("{:?}", i32_add_handling_by_panic(2147483647, 1));
println!("{:?}", i32_add_handling_by_panic(-2147483648, -1));
}
总结起来,我有3个问题:
- 我的类型转换解决方案对强类型语言有效吗? (如果可能的话,我需要解释为什么它有效或无效。)
- 有没有其他更好的方法来处理这个问题?
- panic可以分别处理不同的异常吗?
在这种情况下,Rust 标准库有一个名为 saturating_add
的方法,它支持您的用例:
assert_eq!(10_i32.saturating_add(20), 30);
assert_eq!(i32::MIN.saturating_add(-1), i32::MIN);
assert_eq!(i32::MAX.saturating_add(1), i32::MAX);
在内部,它被实现为 compiler intrinsic。
一般来说,此类问题不打算通过 panics 和 unwinding 来解决,它们仅用于在特殊情况下进行清理。 hand-written 版本可能涉及类型转换,但只计算一次 a as i64 + b as i64
。或者,这里有一个使用 checked_add
的版本,它 returns None
而不是溢出时的恐慌:
fn saturating_add(a: i32, b: i32) -> i32 {
if let Some(sum) = a.checked_add(b) {
sum
} else if a < 0 {
i32::MIN
} else {
i32::MAX
}
}