Is it possible to combine .NET 7 generic math functions that have different constraints?

213 Views Asked by At

Consider the following methods:

static T Divide<T>(T dividend, T divisor) where T : INumber<T>
{
    return dividend / divisor;
}

static T DivideF<T>(T dividend, T divisor) where T : IFloatingPoint<T>
{
    return T.Floor(dividend / divisor);
}

The reason I have two of them is that I want the division to behave the same way regardless of whether the value is an integer or floating point, hence the reason that DivideF calls T.Floor (which is a member of IFloatingPoint<T> and not a member of INumber<T>).

The methods cannot have the same name, because the method signature does not account for the generic constraint, but I would like, if possible to be able to combine these methods into a single method; something like this:

static T Divide<T>(T dividend, T divisor) where T : INumber<T>
{
    if (typeof(T) == typeof(IFloatingPoint<>))
    {
        return T.Floor(dividend / divisor);
    }
    
    return dividend / divisor;
}

This code doesn't work, firstly because typeof(T) == typeof(IFloatingPoint<>) is false even when T is inferred from a value that implements IFloatingPoint<T>, and T.Floor isn't available within the if block anyway.

Is it possible to combine the methods into a single method?

2

There are 2 best solutions below

0
On BEST ANSWER

One not very elegant solution is to implement Floor (in this case, probably very naively).

private static T Floor<T>(T value) where T : INumber<T>
{
    T result = T.Zero;

    while (value > T.Zero)
    {
        if (value < T.One)
        {
            return result;
        }
        
        value -= T.One;
        result++;
    }

    return result;
}

static T Divide<T>(T dividend, T divisor) where T : INumber<T>
{
    return Floor(dividend / divisor);
}

In fact, I could even just implement Divide by hand, so that I don't need Floor...

static T Divide<T>(T dividend, T divisor) where T : INumber<T>
{
    if (divisor == T.Zero)
    {
        throw new DivideByZeroException("Divisor cannot be zero.");
    }

    if (divisor == T.One)
    {
        return dividend;
    }

    if (divisor == -T.One)
    {
        return -dividend;
    }

    T dividendSign = T.IsNegative(dividend) ? -T.One : T.One;
    T divisorSign = T.IsNegative(divisor) ? -T.One : T.One;
    T sign = dividendSign * divisorSign;
    
    dividend = T.Abs(dividend);
    divisor = T.Abs(divisor);

    dividend -= dividend % divisor;
    return dividend / divisor * sign;
}
1
On

This is not possible without a long switch or type conversion.

you can check if the number is floating point via reflection:

bool isFloat = typeof(T).GetInterfaces().Any(x =>
  x.IsGenericType &&
  x.GetGenericTypeDefinition() == typeof(IFloatingPoint<>));

But even then you do not know which one it is. Type conversion might look like this:

static T Divide<T>(T dividend, T divisor) where T : INumber<T> {

    bool isFloat = typeof(T).GetInterfaces().Any(x =>
      x.IsGenericType &&
      x.GetGenericTypeDefinition() == typeof(IFloatingPoint<>));

    if (isFloat) {
        Math.Floor(double.CreateChecked(dividend) / double.CreateChecked(divisor));
    }
    return dividend / divisor;
}