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);
}
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With