Refactoring Celsius converter

Heyo, working through the book right now, specifically at the end of chapter 3 where it has you build a temperature system converter. It works totally fine, but I was enjoying it and decided to refactor and add some stuff like being able to write and output integers (e.g. 87 instead of 87.0). However, the math involved with converting Fahrenheit to Celsius involves floats so it makes it more complicated.

Again, this does work fine. But for refactoring, I've been messing with as for a while, but I can't seem to shrink down turning temperature into a float, doing the math, and turning it back into an integer. I was curious if anyone knew how I could do this more efficiently?

I assume it's at least possible to shrink it down to two lines instead of three

fn main() {
    conversion('c', 87)
}

fn conversion(temperature_type: char, temperature: i32) {
    // Checks if it's supposed to convert to Fahrenheit 
    if temperature_type == 'f' {
        let mut temperature = temperature as f32;
        temperature = temperature * 1.8 + 32.0;
        let temperature = temperature as i32;
        println!("Converted to Fahrenheit: {temperature}");
    // Checks for Celsius
    } else if temperature_type == 'c' {
        let mut temperature = temperature as f32;
        temperature = (temperature - 32.0) / 1.8;
        let temperature = temperature as i64;
        println!("Converted to Celsius: {temperature}");
    } else {
        println!("Please specify 'f' or 'c' in the first argument")
    }
}

Lines of code is a rather bad way of measuring efficiency. The way you are doing the calculation is about as efficient as it gets.
However, if you want a one-liner:

if temperature_type == 'f' {
    let far = (temperature as f32 * 1.8 + 32.0) as i32;
    println!("Converted to Fahrenheit: {far}");
} else if temperature_type == 'c' {
    let cel = ((temperature as f32 - 32.0) / 1.8) as i32;
    println!("Converted to Celcius: {cel}");
} else {
    println!("Please specify 'f' or 'c' in the first argument")
}

Note that because you are converting the final temperature to i32, you are effectively truncating the decimal places off. This might not be what you want.

Converting back to an integer will involve rounding. The as i64 cast you’re doing currently will round towards zero, but maybe you’d prefer more accurate results you get from the round method which rounds to the closest integer instead.

Since you’re only multiplying with fractions, integer arithmetic is an option, too. Importantly, integral division a / b will give the fractional result of ordinary division rounded towards zero. Also, note that 1.8 is 9/5.

So instead of (temperature as f32 * 1.8 + 32.0) as i64, you might as well to temperature * 9 / 5 + 32 and get the same result (unless temperature * 9 overflows).


As for recovering the additional accuracy of (temperature as f32 * 1.8 - 32.0).round() as i64, you can use this approach: the important step in the calculation is the division by 5, and we want to get the result rounded to the nearest integer now, not towards zero. By using .div_euclid(5) we can already get the result always rounded down (“towards negative infinity”, if you will) instead of towards zero, which is easier to work with. (Note that the unstable “div_floor” method might seem more appropriate, but since 5 is positive, the results in this case are going to be the same.) The way to make sure to round to the closest integer instead of down would then be to make the number slightly larger before division.

E.g. rounding down we get, 1/5 == 0, 2/5 == 0, 3/5 == 0, 4/5 == 0, 5/5 == 1, but what we want is to get 1 already for “3/5”, so the solution is to add 2 (which is 5/2 rounded down) before dividing, then we’ll get the results (1 + 2)/5 == 0, (2 + 2)/5 == 0, (3 + 2)/5 == 1, (4 + 2)/5 == 1, (5 + 2)/5 == 1, and so on. Fortunately, with division by an odd number (5, and later also 9), we never have to consider a case where the result is exactly in the middle between two integers. In Rust code, the final result would be

(temperature * 9 + 2).div_euclid(5) + 32

Going the other way involves dividing by 9, so we do the same trick, but add 4 (which is 9/2 rounded down) instead of 2 so (temperature - 32.0) / 1.8 (plus better rounding) becomes

((temperature - 32) * 5 + 4).div_euclid(9)

We could further minimize the number of operations by moving the +- 32 step over the division / multiplication, respectively, and combining it with the +2 or +4 to give

(temperature * 9 + 162).div_euclid(5) // 162 == 2 + 32*5

and

(temperature * 5 - 156).div_euclid(9) // -156 == 4 - 32*5

In case you use these, please double-check my calculation by writing test cases comparing results for a bunch of numbers with the more straightforward implementation using floats and .round().

Thank you for answering! It took me multiple hours but I finally understand your reply, haha.

I tested your final technique against round() (as well as my stable technique just to be sure)- and it works great!

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.