Search code examples
c#.net-generic-math

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


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?


Solution

  • 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;
    }