Как получить абсолютное значение числа в Rust?
Использование метода .abs() для встроенных числовых типов
Отличный вопрос! В Rust абсолютное значение получается через метод .abs(), который реализован для всех числовых типов — как целых (i8, i16, i32, i64, isize), так и с плавающей точкой (f32, f64). Ваш второй пример абсолютно верен:
fn main() {
let x: i32 = -42;
let y = x.abs();
println!("{}", y); // Выведет: 42
let pi: f64 = -3.14;
let abs_pi = pi.abs();
println!("{}", abs_pi); // Выведет: 3.14
}
Почему не работает abs(x)?
В Rust нет глобальной функции abs(), как в Python. Это сделано намеренно: метод привязан к типу, что позволяет компилятору эффективно оптимизировать код. Rust использует трейты для обобщённого программирования.
Универсальная функция с трейтом num::Signed
Если вам нужна функция, работающая с любым знаковым числовым типом, используйте крейт num (популярная библиотека). Добавьте его в Cargo.toml:
[dependencies]
num = "0.4"
Теперь можно написать обобщённую функцию:
use num::Signed;
fn my_abs<T: Signed>(value: T) -> T {
value.abs()
}
fn main() {
let int_val = my_abs(-5i32);
let float_val = my_abs(-3.14f64);
println!("{} {}", int_val, float_val); // 5 3.14
}
Трейт Signed гарантирует, что у типа есть метод .abs(). Это работает для i8, i16, i32, i64, isize, f32, f64.
Без внешних крейтов: ручная реализация
Если не хотите подключать num, можно использовать трейт из стандартной библиотеки std::ops::Neg и условную логику:
fn my_abs<T: PartialOrd + Default + std::ops::Neg<Output = T>>(value: T) -> T {
if value < T::default() {
-value
} else {
value
}
}
fn main() {
println!("{}", my_abs(-5i32)); // 5
println!("{}", my_abs(3.14f64)); // 3.14
}
Но этот подход менее надёжен для f64 из-за NaN (Not a Number). Метод .abs() правильно обрабатывает NaN, а наша ручная реализация — нет.
Важные замечания
- Переполнение для целых:
i32::MIN.abs()вызовет панику, так какi32::MIN = -2147483648, аi32::MAX = 2147483647— абсолютное значение не помещается. Используйтеi64или проверяйте границы. - Для беззнаковых типов (
u8,u32и т.д.) метод.abs()не определён — у них нет знака, значение всегда неотрицательное. - Производительность:
.abs()компилируется в одну инструкцию процессора (например,ABSили битовый трюк), поэтому максимально эффективен.
Вывод: используйте .abs() напрямую или трейт num::Signed для обобщённого кода. Это идиоматично для Rust.
Альтернатива: собственный трейт и макрос
Если вы хотите избежать внешних зависимостей, но нуждаетесь в обобщённой функции, можно определить свой трейт:
trait Absolute {
fn abs(self) -> Self;
}
impl Absolute for i32 {
fn abs(self) -> Self {
if self < 0 { -self } else { self }
}
}
impl Absolute for f64 {
fn abs(self) -> Self {
if self < 0.0 { -self } else { self }
}
}
fn main() {
println!("{}", (-5i32).abs()); // 5
println!("{}", (-3.14f64).abs()); // 3.14
}
Этот подход даёт полный контроль, но требует ручной реализации для каждого типа. Для учебных целей сойдёт, но в продакшене лучше использовать num::Signed.
Для чисел с плавающей точкой: f32::abs и f64::abs
Учтите, что для f32 и f64 есть ещё метод .copysign(), который копирует знак от другого числа:
let x = -3.5f64;
let y = x.copysign(1.0); // = 3.5
println!("{}", y);
Это может быть полезно в научных расчётах, но для простого получения модуля используйте .abs().