Целочисленная операция с границей при переполнении в 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));
}

Код работает хорошо; но мое шестое чувство подсказало мне, что использование преобразования типов проблематично. Таким образом, я попытался использовать традиционную обработку паники (исключений), чтобы справиться с этим ... но я застрял с приведенным ниже кодом (результат паники не может обнаружить переполнение или переполнение):

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. Может ли паника обрабатывать разные исключения отдельно?

person Kir Chou    schedule 03.09.2020    source источник
comment
Знаете ли вы о saturating_add?   -  person L. F.    schedule 03.09.2020
comment
Поймать панику - вероятно, плохая идея по соображениям производительности. Rust-паника не предназначена для использования в качестве исключения для бизнеса, и ее следует выявлять очень редко, если вообще когда-либо. Кроме того, приведение здесь - это простое преобразование, которое одновременно безопасно и четко определено, и в его использовании нет ничего плохого. Опасны преобразования между указателями разных типов, которые даже невозможно разыменовать в безопасном Rust.   -  person user4815162342    schedule 03.09.2020
comment
@ L.F. ???? Спасибо. Я не знал о серии насыщающих функций в примитивном типе. Это именно то, что мне нужно. Я изучу реализацию, отслеживая исходный код позже.   -  person Kir Chou    schedule 03.09.2020
comment
Приведение типов с as может быть проблематичным, поскольку может изменить значение. Чтобы исправить это, используйте преобразование, которое не меняет значение: From. Например, запишите первое условие как if i64::from(a) + i64::from(b) > i64::from(i32::MAX). (То есть, если бы не было очевидно, что проще просто использовать арифметику с насыщением.)   -  person trentcl    schedule 03.09.2020
comment
@trentcl Спасибо. From - еще одно новое для меня знание. Если вы не возражаете, не могли бы вы привести пример того, как значение изменяется после as преобразования типа?   -  person Kir Chou    schedule 03.09.2020
comment
Каждый раз, когда значение не соответствует типу, к которому выполняется приведение. Например, -10_i32 as u32 - это 4294967286, а 1_000_000_000 as i16 - это -13824. Если вы ошиблись с шириной или подписью, вы можете просто молча получить неправильное значение. From не имеет этой проблемы, потому что целые числа реализуют From только при преобразовании без потерь. (Для преобразований, которые подвержены ошибкам, вы можете использовать TryFrom, который возвращает Result, с которым вы можете справиться любым подходящим способом.)   -  person trentcl    schedule 03.09.2020


Ответы (1)


В этом случае в стандартной библиотеке Rust есть метод под названием _1 _, который поддерживает ваш вариант использования:

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

Внутри он реализован как встроенный в компилятор.


В общем, такие проблемы не предназначены для решения с помощью паники и раскрутки, которые предназначены для очистки только в исключительных случаях. Рукописная версия может включать приведение типов, но вычисление 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
    }
}
person L. F.    schedule 03.09.2020