Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to allow function to work with integers or floats?

Tags:

rust

I found a function to compute a mean and have been playing with it. The code snippet below runs, but if the data inside the input changes from a float to an int an error occurs. How do I get this to work with floats and integers?

use std::borrow::Borrow;

fn mean(arr: &mut [f64]) -> f64 {
    let mut i = 0.0;
    let mut mean = 0.0;
    for num in arr {
        i += 1.0;
        mean += (num.borrow() - mean) / i;
    }
    mean
}

fn main() {
    let val = mean(&mut vec![4.0, 5.0, 3.0, 2.0]);
    println!("The mean is {}", val);
}
like image 493
atomsmasher Avatar asked Mar 20 '17 05:03

atomsmasher


People also ask

How do you add integers and floats?

Of course you can add an int to float . C will convert the int to float before adding. However, in the expression (float)15/2 + 15/2 , the first 15/2 will be calculated as float and the second 15/2 will be an integer division first, and that result converted to float. So you'll be adding 7.5+7.

Why use integers or floats and not just floats all the time?

Another reason to favour integers over floats is performance and efficiency. Integer arithmetic is faster. And for a given range integers consume less memory because integers don't need to represent non-integer values.


1 Answers

The code in the question doesn't compile because f64 does not have a borrow() method. Also, the slice it accepts doesn't need to be mutable since we are not changing it. Here is a modified version that compiles and works:

fn mean(arr: &[f64]) -> f64 {
    let mut i = 0.0;
    let mut mean = 0.0;
    for &num in arr {
        i += 1.0;
        mean += (num - mean) / i;
    }
    mean
}

We specify &num when looping over arr, so that the type of num is f64 rather than a reference to f64. This snippet would work with both, but omitting it would break the generic version.

For the same function to accept floats and integers alike, its parameter needs to be generic. Ideally we'd like it to accept any type that can be converted into f64, including f32 or user-defined types that defin such a conversion. Something like this:

fn mean<T>(arr: &[T]) -> f64 {
    let mut i = 0.0;
    let mut mean = 0.0;
    for &num in arr {
        i += 1.0;
        mean += (num as f64 - mean) / i;
    }
    mean
}

This doesn't compile because x as f64 is not defined for x of an arbitry type. Instead, we need a trait bound on T that defines a way to convert T values to f64. This is exactly the purpose of the Into trait; every type T that implements Into<U> defines an into(self) -> U method. Specifying T: Into<f64> as the trait bound gives us the into() method that returns an f64.

We also need to request T to be Copy, to prevent reading the value from the array to "consume" the value, i.e. attempt moving it out of the array. Since primitive numbers such as integers implement Copy, this is ok for us. Working code then looks like this:

fn mean<T: Into<f64> + Copy>(arr: &[T]) -> f64 {
    let mut i = 0.0;
    let mut mean = 0.0;
    for &num in arr {
        i += 1.0;
        mean += (num.into() - mean) / i;
    }
    mean
}

fn main() {
    let val1 = mean(&vec![4.0, 5.0, 3.0, 2.0]);
    let val2 = mean(&vec![4, 5, 3, 2]);
    println!("The means are {} and {}", val1, val2);
}

Note that this will only work for types that define lossless conversion to f64. Thus it will work for u32, i32 (as in the above example) and smaller integer types, but it won't accept for example a vector of i64 or u64, which cannot be losslessly converted to f64.

Also note that this problem lends nicely to functional programming idioms such as enumerate() and fold(). Although outside the scope of this already longish answer, writing out such an implementation is an exercise hard to resist.

like image 172
user4815162342 Avatar answered Sep 21 '22 05:09

user4815162342