简体   繁体   中英

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!

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 usize . This version of your program, with a few added bounds and .clone() s, compiles:

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: usize) -> Vec<T>
where
    T: Sub<Output = T> + Add<Output = T> + Div<Output = T> + Clone + Debug,
    usize: TryInto<T>,
    <usize as TryInto<T>>::Error: Debug,
{
    let segments: T = (n - 1)
        .try_into()
        .expect("requested number of elements did not fit into T");
    let dx = (xend - x0.clone()) / segments;

    let mut x = vec![x0; n];

    for i in 1..n {
        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 .)

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()
}

Playground

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM