【问题标题】:Integer operation with boundary when overflow in RustRust 中溢出时带边界的整数运算
【发布时间】:2020-09-03 08:36:25
【问题描述】:

我最近遇到的问题是需要根据整数类型的位做带边界的整数运算。

比如用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 cast 是有问题的。因此,我尝试使用传统的恐慌(异常)处理来处理这个问题......但我坚持使用下面的代码(恐慌结果无法检测下溢或溢出):

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个问题:

  1. 我的类型转换解决方案对强类型语言有效吗? (如果可能,我需要解释为什么它有效或无效。)
  2. 还有其他更好的方法来处理这个问题吗?
  3. panic 能否分别处理不同的异常?

【问题讨论】:

  • 你知道saturating_add吗?
  • 出于性能原因,捕捉恐慌可能不是一个好主意。 Rust 恐慌不是为用作业务异常而设计的,应该很少被捕获,如果有的话。还有,这里的强制转换只是一个简单的转换,既安全又定义明确,使用也没有什么问题。不同类型的指针之间的强制转换是危险的,在安全的 Rust 中甚至不可能取消引用。
  • @L.F. ???谢谢。我不知道原始类型中的一系列饱和函数。一个正是我需要的。稍后我将通过跟踪源代码来学习实现。
  • 使用as 进行类型转换可能会出现问题,因为它可以更改值。解决方法是使用不改变值的转换:From。例如,将第一个条件写为if i64::from(a) + i64::from(b) &gt; i64::from(i32::MAX)。 (也就是说,如果使用饱和算术显然不是更容易的话。)
  • 任何时候值不适合被强制转换的类型。例如,-10_i32 as u3242949672861_000_000_000 as i16-13824。如果你碰巧弄错了宽度或符号,你可能只是默默地得到了错误的值。 From 没有这个问题,因为整数只有在无损转换时才实现From。 (对于容易出错的转换,您可能需要使用TryFrom,它会返回一个Result,您可以以任何合适的方式进行处理。)

标签: rust integer-overflow panic


【解决方案1】:

在这种情况下,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


一般来说,此类问题不打算通过恐慌和展开来解决,它们仅用于在特殊情况下进行清理。手写版本可能涉及类型转换,但只计算一次a as i64 + b as i64。或者,这里有一个使用checked_add 的版本,它返回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
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-12
    相关资源
    最近更新 更多