2

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?

Guru Stron
  • 102,774
  • 10
  • 95
  • 132
Qrt
  • 389
  • 3
  • 16

1 Answers1

2

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.

Guru Stron
  • 102,774
  • 10
  • 95
  • 132
  • Thanks, did not know about the CreateXX methods. Now with the proper way to cast, I can even use the Math.Round method again. – Qrt Feb 28 '23 at 15:57
  • @Qrt was glad to help! Note that it is not actually casting and can behave a bit differently compared to it (an example can be found [here](https://stackoverflow.com/q/75593871/2501279)) – Guru Stron Feb 28 '23 at 15:59