0

Как умножать/делить/складывать/вычитать числа разных типов?

1

Я работаю с вторым изданием справочника по Rust и решил попробовать создать классический конвертер градусов Цельсия в Фаренгейты.

Вот мой код:

fn c_to_f(c: f32) -> f32 {
    return ( c * ( 9/5 ) ) + 32;
}

При компиляции этого кода с помощью cargo build возникает ошибка времени компиляции:

error[E0277]: the trait bound `f32: std::ops::Mul<{integer}>` is not satisfied
 --> src/main.rs:2:12
  |
2 |     return (c * (9 / 5)) + 32;
  |            ^^^^^^^^^^^^^ the trait `std::ops::Mul<{integer}>` is not implemented for `f32`
  |
  = note: no implementation for `f32 * {integer}`

Как новичок в Rust, я интерпретирую это как то, что нельзя умножать числа с плавающей точкой и целые числа. Я решил эту проблему, сделав все свои константы числами с плавающей точкой:

fn c_to_f(c: f32) -> f32 {
    return ( c * ( 9.0 / 5.0 ) ) + 32.0;
}

Тем не менее, у меня остались сомнения. Поскольку я пришёл из языков C/C++/Java/Python, мне было удивительно узнать, что нельзя просто выполнять арифметические операции над числами разных типов. Правильно ли это — просто преобразовывать их в один и тот же тип, как я сделал в данном случае?

1 ответ(ов)

0

TL;DR: as — это самый распространенный способ преобразования между примитивными числовыми типами, но его использование требует размышлений.

fn c_to_f(c: f32) -> f32 {
    (c * (9 as f32 / 5 as f32)) + 32 as f32
}

В этом примере разумнее сразу использовать литералы с плавающей запятой:

fn c_to_f(c: f32) -> f32 {
    (c * (9. / 5.)) + 32.
}

Настоящая проблема заключается в том, что выполнение арифметики с перемешанными типами несколько усложнено.

Когда вы умножаете1 T на T, вы обычно ожидаете получить результат типа T, по крайней мере, для базовых типов.

Однако при смешивании типов возникают некоторые сложности:

  • смешивание знаков (signedness),
  • смешивание точности (precision).

Так, например, какой идеальный результат у выражения i8 * u32? Наименьший тип, который может вместить все значения i8 и u32, — это i64. Должен ли он быть результатом?

В качестве другого примера, какой идеальный результат у f32 * i32? Наименьший тип, который может вместить все значения f32 и i32, — это f64. Должен ли он быть результатом?

Идея такого расширения мне кажется довольно запутанной. Это также имеет влияние на производительность (операции с f32 могут выполняться значительно быстрее, чем операции с f64, особенно при векторизации).

Из-за этих проблем Rust на данный момент требует от вас быть четким: какой тип вы хотите использовать для вычисления? Какой тип имеет смысл в вашей конкретной ситуации?

А затем приводите типы должным образом, используя as, и учитывайте, какой режим округления применять (.round(), .ceil(), .floor() или .trunc() при переходе от числа с плавающей запятой к целочисленному).

1 Сложение, вычитание и деление работают аналогичным образом.

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь