Numeric constants in generic math code

I have to write some long and winded math code which needs to work with f32 and f64 and there are plenty of constants like 2 and 6 inside it. Example:

fn foo<T>(x: T, y: T, z: T)
where
    T: Float,
    u32: Into<T>,
{
    x * x * ((y + z) / 3.into() + y * y) / 4.into()
        + ((z - x) / 2.into() + 6.into() * y * y - x * x * x) / 3.into();
}

Initially I had troubles with the constants, but found a way to do it with Into.

Is that the recommended way to deal with constants? The *.into() expressions are cluttering the code and make it hard to spot implementation mistakes..

Mathematically speaking, 4 is 1 + 1 + 1 + 1 by definition. So the most correct code would be

let four = T::one() + T::one() + T::one() + T::one();

And yeah, it's tedious to read/write. The only real problem I see on the OP's example is that f32 can't be .into()-ed from the u32. Since all the constants are pretty small I'd use T: From<u8> instead.

The suggestion to replace u32 with u8 sounds great.

Is there no way to write Rust code that the compiler can derive that 4 means a numeric constant with value 4.0? I only need it for floating point types (f32 and f64).

If you really only need it for those two, it's often much easier to just make a simple macro to implement it for the two types specifically, rather than via generics.

1 Like

make a simple macro to implement it

How would I do that?

Example.

2 Likes

Thank you, I will try out the macro approach.

Why don't you just write 4.0 in the first place? That will already be deduced to have type f32 or f64.

@danvil is trying to write a literal for a generic type T, which is f32 or f64 in practice. As far as I know, there's no way to just use a float literal in this case— There's no trait bound for "can be represented by a float literal."

The closest you can get is the macro approach which uses the same textual implementation twice, once for each type. This does involve "just writing 4.0 in the first place," as can be seen in @quinedot's example:

macro_rules! impl_foo {
    ($($t:ty),* $(,)?) => {$(
        impl Foo for $t {
            fn foo(x: Self, y: Self, z: Self) -> Self {
                x * x * ((y + z) / 3.0 + y * y) / 4.0
                    + ((z - x) / 2.0 + 6.0 * y * y - x * x * x) / 3.0
                
            }
        }
    )*}
}

I get that, but s/he already writes .into(). I was trying to recommend 4.0.into() because that works for both f32 and f64 already.

1 Like

4.into() also works for both f32 and f64. I am trying to reduce the clutter in math code. The closer it is to a "pure" mathematical formula the easier it is for me to spot mistakes.