Search code examples
c#genericsc#-11.0.net-generic-math

How can I do simple math with a generic INumber in .NET 7 / C#11?


I like to program a histogram function using a class with a generic data type bound to an INumber in .NET 7/C# 11 as

public class Histogram<TValue> where TValue : INumber<TValue>

public List<KeyValuePair<int,  int>> CalcHistogram(List<TValue> Values, TValue CorrectValue, TValue BinSize)
{
        // ...

        foreach (TValue value in Values)
        {
                // version 1
                // calc bin number as TValue type
                TValue bn = (value - CorrectValue) / BinSize;

                // round to nearest int without Math.Round
                int binNumber = bn < 0 ? (int)(bn - 0.5) : (int)(bn + (TValue)0.5);

                // version 2
                // calc bin number
                int binNumber2  = (int)Math.Round((value - CorrectValue) / BinSize, MidpointRounding.AwayFromZero);

                // ....

But I get multiple errors I don't understand:

  1. bn < 0 results in a CS0019 error (Operator '<' cannot be applied to operands of type 'TValue' and 'int'), although I thought that IComparable should do that. Casting 0 to TValue does not work as in 3.

  2. bn - 0.5 results in a CS0019 error (Operator '-' cannot be applied to operands of type 'TValue' and 'double'), although I thought I could subtract two "numbers".

  3. (TValue)0.5 results in a CS0030 error (Cannot convert type 'double' to 'TValue'), although I thought that casting a double to a "number" should be possible, after all, double implements INumber.

  4. (binNumber * BinSize) results in a CS0019 error (Operator '*' cannot be applied to operands of type 'int' and 'TValue').

  5. (int)Math.Round((value - CorrectValue) / BinSize, MidpointRounding.AwayFromZero) results in CS1503 (Argument 1: cannot convert from TValue to decimal) and CS1503 (Argument 2: cannot convert from System.MidpointRouting to int).

Can't I use the generic INumber as a "number" and do whatever I could do with a float or a double like casting, arithmetic operations +-*/, comparing?


Solution

  • Generic math allows to operate on numbers of the same type (check out the interfaces, they are usually defined using curiously recurring template pattern like INumber<TSelf> : IComparable<TSelf>), so you need to match types for operations.

    bn < 0 results in a CS0019 error (Operator '<' cannot be applied to operands of type 'TValue' and 'int'), although I thought that IComparable should do that

    This can be bn < TValue.Zero

    bn - 0.5 results in a CS0019 error (Operator '-' cannot be applied to operands of type 'TValue' and 'double'), although I thought I could subtract two "numbers".

    This one - bn - TValue.CreateChecked(0.5) (or other INumber<TSelf>.CreateX methods)

    (TValue)0.5

    TValue.CreateChecked again

    (binNumber * BinSize)

    (TValue.CreateChecked(binNumber) * BinSize)

    (int)Math.Round((value - CorrectValue) / BinSize, MidpointRounding.AwayFromZero)

    rounding is defined for IFloatingPoint<TSelf> - IFloatingPoint<TSelf>.Round

    And "cast" from the TValue to int can be done like this:

    int.CreateChecked(binNumber)
    

    Note that CreateChecked will throw in case of overflow.