Jens
Jens

Reputation: 25563

Why don't number types share a common interface?

I recently ran across the problem, that I wanted a function to work on both doubles and integers and wondered, why there is no common interface for all number types (containing arithmetic operators and comparisons).

It would make writing functions like Math.Min (which exist in a gazillion overloads) way more convenient.

Would introducing an additional interface be a breaking change?

Edit: I think about using this like

public T Add<T>(T a, T b) where T: INumber
{
    return a+b;
}

or

public T Range<T>(T x, T min, T max) where T:INumber
{
    return Max(x, Min(x, max), min);
}

Upvotes: 34

Views: 12203

Answers (10)

lidqy
lidqy

Reputation: 2463

As of .NET 7 this feature seems more than satisfied.

Build-in numeric types in .NET now do support any basic numeric, arithmetic and maths operation including some more advanced, through interfaces.

Moreover, some of these interface expose operator overloading. So you can use "natural syntax" for e.g. addition using the "+"-op and not a method call like "Add()".

System.Int32 for example in .NET 7 implements as many as 21 numerical interfaces, which are the following (see below). So maths can be done now using generics. I do not know how the runtime experience is, just read the docs ...

public readonly struct Int32 : IComparable, IConvertible, IEquatable, IParsable, ISpanParsable, System.Numerics.IAdditionOperators<int,int,int>, System.Numerics.IAdditiveIdentity<int,int>, System.Numerics.IBinaryInteger, System.Numerics.IBinaryNumber, System.Numerics.IBitwiseOperators<int,int,int>, System.Numerics.IComparisonOperators<int,int,bool>, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators<int,int,int>, System.Numerics.IEqualityOperators<int,int,bool>, System.Numerics.IIncrementOperators, System.Numerics.IMinMaxValue, System.Numerics.IModulusOperators<int,int,int>, System.Numerics.IMultiplicativeIdentity<int,int>, System.Numerics.IMultiplyOperators<int,int,int>, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.IShiftOperators<int,int,int>, System.Numerics.ISignedNumber, System.Numerics.ISubtractionOperators<int,int,int>, System.Numerics.IUnaryNegationOperators<int,int>, System.Numerics.IUnaryPlusOperators<int,int>

https://learn.microsoft.com/de-de/dotnet/api/system.numerics?view=net-7.0#interfaces

https://learn.microsoft.com/en-us/dotnet/api/system.int32?view=net-7.0

Upvotes: 0

Bowen
Bowen

Reputation: 563

Finally, starting from .NET 7 you can use INumber<TSelf> Interface under namespace System.Numerics:

static T Add<T>(T left, T right) where T : INumber<T>
{
    return left + right;
}

Upvotes: 3

Saeed Amiri
Saeed Amiri

Reputation: 22555

How to compute 2 + 2.35? Is it 4 or 4.35 or 4.349999? How interface can find the appropriate output type? You may write your own extension method and use overloading to solve a specific problem, but if we want to have an interface that handles all edge cases, then just imagine how long will be the size of the interface and as a consequence finding a useful function is practically impossible. Additionally, the interface adds overheads and numbers are usually the basis of calculation so we can't rely on slow operations.

Here is how you can write your own extension class:

public static class ExtensionNumbers
{
    public static T Range<T>(this T input, T min, T max) where T : class 
    {
        return input.Max(input.Min(max), min);
    }

    public static T Min<T>(this T input, params T[] param) where T : class
    {
        return null;
    }

    private static T Max<T>(this T input, params T[] number) where T : class 
    {
        return null;
    }      

}

I used where T : class just to be able to compile it.

Upvotes: 4

Dirk Vollmar
Dirk Vollmar

Reputation: 176179

Update

Generic math is coming as a preview feature to .NET 6. Read the announcement in the .NET dev blog:

https://devblogs.microsoft.com/dotnet/preview-features-in-net-6-generic-math/


If you want to do such kind of "generic" arithmetics your option in a strongly-typed language such as C# are quite limited. Marc Gravell described the problem as follows:

.NET 2.0 introduced generics into the .NET world, which opened the door for many elegant solutions to existing problems. Generic constraints can be used to restrict the type-arguments to known interfaces etc, to ensure access to functionality - or for simple equality/inequality tests the Comparer<T>.Default and EqualityComparer<T>.Default singletons implement IComparer<T> and IEqualityComparer<T> respectively (allowing us to sort elements for instance, without having to know anything about the "T" in question).

With all this, though, there is still a big gap when it comes to operators. Because operators are declared as static methods, there is no IMath<T> or similar equivalent interface that all the numeric types implement; and indeed, the flexibility of operators would make this very hard to do in a meaningful way. Worse: many of the operators on primitive types don't even exist as operators; instead there are direct IL methods. To make the situation even more complex, Nullable<> demands the concept of "lifted operators", where the inner "T" describes the operators applicable to the nullable type - but this is implemented as a language feature, and is not provided by the runtime (making reflection even more fun).

However, C# 4.0 introduced the dynamic keyword which you can use to choose the correct overload at runtime:

using System;

public class Program
{
    static dynamic Min(dynamic a, dynamic b)
    {
        return Math.Min(a, b);        
    }

    static void Main(string[] args)
    {
        int i = Min(3, 4);
        double d = Min(3.0, 4.0);
    }
}

You should be aware that this removes type-safety and you might get exceptions at runtime if the dynamic runtime cannot find a suitable overload to call, e.g. because you mixed types.

If you want to get type-safety you might want to have a look at the classes available in the MiscUtil library providing generic operators for basic operations.

Please note that if you are only after specific operations you actually might use the interfaces that the built-in types already implement. For example, a type-safe generic Min function could look like this:

public static T Min<T>(params T[] values) where T : IComparable<T>
{
    T min = values[0];
    foreach (var item in values.Skip(1))
    {
        if (item.CompareTo(min) < 0)
            min = item;
    }
    return min;
}

Upvotes: 16

Dan
Dan

Reputation: 7724

They soon will support a common interface!

Check out this .NET Blog post.

Starting in .NET 6 (preview 7 I think), you will be able to make use of interfaces such as INumber and IFloatingPoint to create programs such as:

using System;

Console.WriteLine(Sum(1, 2, 3, 4, 5));
Console.WriteLine(Sum(10.541, 2.645));
Console.WriteLine(Sum(1.55f, 5, 9.41f, 7));

static T Sum<T>(params T[] numbers) where T : INumber<T>
{
    T result = T.Zero;

    foreach (T item in numbers)
    {
        result += item;
    }

    return result;
}

INumber currently comes from the System.Runtime.Experimental NuGet package. My project file for the sample above looks like

<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <EnablePreviewFeatures>true</EnablePreviewFeatures>
        <OutputType>Exe</OutputType>
        <TargetFramework>net6.0</TargetFramework>
        <Nullable>enable</Nullable>
        <LangVersion>preview</LangVersion>
    </PropertyGroup>

    <ItemGroup>
        <PackageReference Include="System.Runtime.Experimental" Version="6.0.0-preview.7.21377.19" />
    </ItemGroup>

</Project>

There are also interfaces such as IAdditionOperators and IComparisonOperators so you can make use of specific operators generically.

Upvotes: 3

Marc Gravell
Marc Gravell

Reputation: 1062945

It isn't as simple as introducing an interface, as the operators available are different per type, and are not always even homogeneous (i.e. DateTime + TimeSpan => DateTime, DateTime - DateTime => TimeSpan).

At the techincal level, there may be lot of boxing etc involved here, plus regular operators are static.

Actually, for Min / Max, Comparer<T>.Default.Compare(x,y) does pretty much everything you would hope.

For other operators: in .NET 4.0 dynamic is of great help here:

T x = ..., y = ...;
T sum = (dynamic)x + (dynamic)y;

but otherwise "MiscUtil" has generic support for operators via the Operator class, i.e.

T x = ..., y = ...;
T sum = Operator.Add(x, y); // actually Add<T>

Upvotes: 2

leppie
leppie

Reputation: 117250

Going with Matthew's answer, do notice the difference between the 3 invocations.

void DoSomething(ref MyStruct something)
{
  something.DoSomething();
} 

static void Main(string[] args)
{
  var s = new MyStruct(10);
  var i = (IDoSomething)s;

  DoSomething(s); // will not modify s
  DoSomething(i); // will modify i
  DoSomething(ref s); // will modify s, but with no reassignment

}

Upvotes: 0

rerun
rerun

Reputation: 25505

The problem is that in the architecture of how numbers are stored different number types are treated fundimentaly differently. For starters your verbiage is wrong and interfaces won't work, but what I think your getting at is you want numerics to be loosely typed.

Just for a start as to why you wouldn't want to do this consider Integer types are a one to one mapping on the range of values they can represent while floating point types have a persision and and exponent component since there are infantly many floating point numbers. The language developers would have to make some very fundamental and potentially error causing assumptions in the language design.

Take a look at this article about floating point math for more info.

Upvotes: 0

Matthew Abbott
Matthew Abbott

Reputation: 61599

Well, you can't define operators in interfaces anyway and structs (although they support interfaces) wouldn't work well through interface implementations, as this would require boxing and unboxing, which of course would be a big performance hit when performing mathmatical operations purely through interface implementations.

I would also highlight that when you cast a struct to its interface, the result object is a reference type (a boxed object) which you perform operations on, not the original struct itself:

interface IDoSomething
{
  void DoSomething();
}

struct MyStruct : IDoSomething
{
  public MyStruct(int age)
  {
    this.Age = age;
  }

  public int Age;

  pubblic void DoSomething()
  {
    Age++;
  }
}

public void DoSomething(IDoSomething something)
{
  something.DoSomething();
} 

When I pass in my instance of my struct, its boxed (becomes a reference type) which I perform my DoSomething operation on, but my original instance of my struct will not change.

Upvotes: 1

Aurelien Ribon
Aurelien Ribon

Reputation: 7634

That's the main characteristic of a "strongly-typed language". This is something that avoids billions of mistakes a minute. Of course we want int to be totally different as double.

Upvotes: -4

Related Questions