'Rust generic linspace function
I am trying to implement a generic function linspace:
pub fn linspace<T> (x0: T, xend: T, n: usize) -> Vec<T>
where
T: Sub<Output = T>
+ Add<Output = T>
+ Div<Output = T>
+ Clone
{
let dx = (xend - x0) / ((n - 1) as T);
let mut x = vec![x0; n];
for i in 1..n {
x[i] = x[i - 1] + dx;
}
x
}
So far I have figured out that T must implement Sub, Add, Div and Clone, but now I am having issues with the n as T statement.
non-primitive cast: `usize` as `T`
let dx = (xend - x0) / ((n - 1) as T);
| ^^^^^^^^^^^^^ an `as` expression can only be used to convert between primitive types or to coerce to a specific trait object
I am aware of the num crate, but I am trying to implement this without external crates. Is there a workaround? Thanks!
Solution 1:[1]
Unless you're doing this as a learning exercise, I recommend that you require the bounds from the num_traits crate, which has traits like Float that would come useful here:
use num_traits::Float;
pub fn linspace<T: Float + TryFrom<usize>>(x0: T, xend: T, n: usize) -> Vec<T> {
let dx = (xend - x0) / (n - 1).try_into().unwrap_or_else(|_| panic!());
let mut x = vec![x0; n];
for i in 1..n {
x[i] = x[i - 1] + dx;
}
x
}
yet now I am getting the error: the trait 'From' is not implemented for 'f64'.
It is not implemented because there are usize values that cannot be exactly represented as f64. The error is making you decide what to do with those. My code panics if such a value is encountered.
Also, I believe floating-point additions can accumulate errors, so a calculation based on multiplication might be a better idea:
pub fn linspace<T: Float + TryFrom<usize>>(x0: T, xend: T, n: usize) -> Vec<T> {
let to_float = |i: usize| i.try_into().unwrap_or_else(|_| panic!());
let dx = (xend - x0) / to_float(n - 1);
(0..n).map(|i| x0 + to_float(i) * dx).collect()
}
Solution 2:[2]
If you want to stick to standard library traits, you will need to use TryInto, and deal with the fact that the conversion requested could fail because the number type could be smaller than the inputs. Also, unfortunately, there isn't even any fallible TryInto<f32> for numbers that don't necessarily fit in f32 (or f64). So, we have to make the number of items u16 which can be converted to any float type.
use core::ops::Add;
use core::ops::Div;
use core::ops::Sub;
use std::fmt::Debug;
pub fn linspace<T>(x0: T, xend: T, n: u16) -> Vec<T>
where
T: Sub<Output = T> + Add<Output = T> + Div<Output = T> + Clone + Debug,
u16: TryInto<T> + TryInto<usize>,
<u16 as TryInto<T>>::Error: Debug,
{
let segments: T = (n - 1)
.try_into()
.expect("requested number of elements did not fit into T");
let n_size: usize = n.try_into()
.expect("requested number of elements exceeds usize");
let dx = (xend - x0.clone()) / segments;
let mut x = vec![x0; n_size];
for i in 1..n_size {
x[i] = x[i - 1].clone() + dx.clone();
}
x
}
It will panic if given a too-large n, for example if T is u8 and n is 1000.
(By the way, repeatedly adding dx is not usually the best way to do this, because it can accumulate error if T is a floating-point type; the last element will not necessarily be equal to xend.)
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 |
