Generics: Using either f32 or f64

As far as I can make out, accepting either f32 or f64 as arguments to a function isn’t easy in practice. The problem is using constants. For example, this won’t work:

use num_traits::float::Float;
fn foo<T: Float>(n: T) -> T {
    n * 3.2
}

So I think my options:

  1. Cast from an f32 literal into type T. Could this introduce errors?
  2. Use a trait to add the constants to both f32 and f64. Not nice.
  3. Use a macro to create foo_f32 and foo_f64. Or put each function into a separate module e.g. bar::f32::foo and bar::f64::foo.

Are there any other solutions I’m not thinking of? None of these seem very appealing.

1 Like

Nope, there isn’t anything that’s both nice and truly generic.

If it’s OK to use larger type, then n: impl Into<f64> and let n = n.into(); lets you have generic parameter and do computation on f64. You won’t be able to return the original type though, because there are no lossless conversions the other way.

This method could be implemented with bound T: Mul<Output=T> + From<f32> and n * 3.2_f32.into(), but having to declare std::ops is massive PITA.

1 Like

If that can be of any help, most of my numerical projects in C++ and Rust approach this problem in a slightly different way:

// Floating-point precision is configured here
#[cfg(feature = "f32")]
pub type Float = f32;
#[cfg(feature = "f32")]
pub use std::f32 as floats;
#[cfg(not(feature = "f32"))]
pub type Float = f64;
#[cfg(not(feature = "f32"))]
pub use std::f64 as floats;
pub type Complex = num_complex::Complex<Float>;

Obviously, this approach is only suitable for applications, not libraries. But it also has a major advantage: not every function that manipulates floating-point data needs to be generic.

2 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.