Home > database >  Is it possible to create a generic mathematical function without using the num_traits crate?
Is it possible to create a generic mathematical function without using the num_traits crate?

Time:03-31

I would like to define a square root function, that is generic over all numerical types that can be casted into an f32. I am aware, that my current approach can suffer precision issues, but I am mainly interested in the concept for how I can create such a function.

Here is my current approach:

fn integer_sqrt<T>(num: T) -> T where T: From<f32>, f32: From<T>{
    f32::from(num).sqrt().into()
}

The function itself does compile, but it is hardly of any use, because when I for example try to call the function using an u32, I get the following compilation errors:

error[E0277]: the trait bound `u32: From<f32>` is not satisfied
 --> src/main.rs:2:28
  |
2 |     let res = integer_sqrt(25u32);
  |               ------------ ^^^^^ the trait `From<f32>` is not implemented for `u32`
  |               |
  |               required by a bound introduced by this call
  |
  = help: the following implementations were found:
            <u32 as From<Ipv4Addr>>
            <u32 as From<NonZeroU32>>
            <u32 as From<bool>>
            <u32 as From<char>>
          and 2 others
note: required by a bound in `integer_sqrt`
 --> src/main.rs:6:42
  |
6 | fn integer_sqrt<T>(num: T) -> T where T: From<f32>, f32: From<T>{
  |                                          ^^^^^^^^^ required by this bound in `integer_sqrt`

error[E0277]: the trait bound `f32: From<u32>` is not satisfied
 --> src/main.rs:2:15
  |
2 |     let res = integer_sqrt(25u32);
  |               ^^^^^^^^^^^^ the trait `From<u32>` is not implemented for `f32`
  |
  = help: the following implementations were found:
            <f32 as From<i16>>
            <f32 as From<i8>>
            <f32 as From<u16>>
            <f32 as From<u8>>
note: required by a bound in `integer_sqrt`
 --> src/main.rs:6:58
  |
6 | fn integer_sqrt<T>(num: T) -> T where T: From<f32>, f32: From<T>{
  |                                                          ^^^^^^^ required by this bound in `integer_sqrt`

For more information about this error, try `rustc --explain E0277`.
error: could not compile `playground` due to 2 previous errors

I do understand why I get these errors, however I do not understand why <f32 as From<u32>> and <u32 as From<f32>> are not implemented, when I can easily cast between them using as.

Here is a Playground illustrating the problem.

Is there any way to achieve the desired behaviour without using the num-traits crate?

CodePudding user response:

I usually solve this with macro and custom trait:

pub trait IntSqrt {
    fn isqrt(self) -> Self;
}

macro_rules! impl_int_sqrt {
    ($($t:ty)*) => {
        $(
            impl IntSqrt for $t {
                #[inline]
                fn isqrt(self) -> Self {
                    (self as f64).sqrt() as Self
                }
            }
        )*
    };
}

impl_int_sqrt!(i8 i16 i32 i64 i128 isize u8 u16 u32 u64 u128 usize);

#[cfg(test)]
#[test]
fn test() {
    assert!(1i8.isqrt() == 1);
    assert!(2i16.isqrt() == 1);
    assert!(3u32.isqrt() == 1);
    assert!(4usize.isqrt() == 2);
    assert!(5.isqrt() == 2);
    assert!(6.isqrt() == 2);
    assert!(7.isqrt() == 2);
    assert!(8.isqrt() == 2);
    assert!(9.isqrt() == 3);
}

CodePudding user response:

There is an open RFC for that: RFC 2484: FromLossy and TryFromLossy trait.

In the meantime, you can use num-traits's AsPrimitive trait:

fn integer_sqrt<T>(num: T) -> T
where
    T: AsPrimitive<f32>,
    f32: AsPrimitive<T>,
{
    num.as_().sqrt().as_()
}
  • Related