Generic math in .NET

Antão Almada
3 min readJun 10, 2023

--

“Sculpture by Liechennay” by aalmada

Before .NET 7

Some .NET languages like C# support the definition of arithmetic operators. These can be used to write math expressions using the familiar arithmetic operators (+, -, *, /) instead of calling methods (Add, Subtract, Multiply, Divide).

For math operations that are not expressed as operators, like trigonometric operations, .NET provides the System.Math static class where most methods only handle double precision (double) values. There are scenarios where using single precision (float) may be better so since the release of .NET Core 2 there’s also a System.MathF.

Any type can define its custom arithmetic operators but it was hard to define them using generics because these only allow calling known methods. Operators are static methods and they could not be defined in an interface. Defining arithmetic operators for generic types like Vector<T> was possible but very cumbersome and hard to maintain. For this reason, most libraries define Vector types only for one numerical type.

.NET 7 and beyond!

.NET 7 with C# 11 brings several new features breaking the previous limitations. It is now possible to declare static virtual methods in interfaces which include the arithmetic operators.

The namespace System.Numerics now includes many interfaces that declare sets of mathematical operations defined by the native numerical .NET types. For example, a type that implements IAdditionOperators<TSelf, TOther, TResult>, has the operator + implemented.

Now you can implement the following generic Sum method:

static T Sum<T>(IEnumerable<T> source)
where T: IAdditiveIdentity<T, T>, IAdditionOperators<T, T, T>
{
var sum = T.AdditiveIdentity; // initialize to zero
foreach(var value in source)
sum += value; // add value to sum
return sum;
}

This method can be used to sum a collection of any type that defines the additive identity (zero) and the operator + (used by the +=). This includes all .NET native numerical types and can also include other types like vectors, quaternions, matrices, and many more.

NOTE: You can restrict T to INumber<T>, allowing any numeric native type to be used, or you could restrict to IFloatingPoint<T>, allowing any floating point native type to be used. Yet, leaving the method like it is, allows it to be used by types that don’t implement all the interfaces required by INumber<TSelf> or IFloatingPoint<TSelf>. These are the bear minimums required and will support several more types.

System.Numerics includes new interfaces for math operations other than arithmetic operators, for example ITrigonometricFunctions<TSelf>. This interface is implemented by some of the native numerical types in .NET. You can now write the following:

// half-precision floating-point
var sinHalf = Half.Sin(Half.Pi);
var arcSinHalf = Half.Asin(sinHalf);

// single-precision floating-point
var sinFloat = float.Sin(float.Pi);
var arcSinFloat = float.Asin(sinFloat);

// double-precision floating-point
var sinDouble = double.Sin(double.Pi);
var arcSinDouble = double.Asin(sinDouble);

Notice that the Pi constant, the Sin and Asin methods are defined for the floating-point types Half, float and double. This means that you no longer need or should even use either System.Math or System.MathF. Also, more numeric types are now supported.

System.Numerics provides several more similar interfaces: IExponentialFunctions<TSelf>, IHyperbolicFunctions<TSelf>, ILogarithmicFunctions<TSelf>, IPowerFunctions<TSelf>, IRootFunctions<TSelf>, and more.

The constants E, Pi, and Tau are defined in the interface IFloatingPointConstants<TSelf>.

Conclusions

It is now possible to use generics in many more use cases. This makes code much cleaner and easier to maintain.

You should stop using System.Math or System.MathF and take advantage of the math provided by each type.

If you implement your custom numeric type, you should implement the interfaces provided in System.Numerics. This way, your type can be used in third-party developed methods.

I’ve been developing an open-source library that uses .NET generic math to implement primitives in several coordinate system: cartesian 2D and 3D, polar, spherical, and geodetic. It implements them as immutable value types, using generics to avoid unit mismatch execution errors.

Check it out at https://netfabric.github.io/NetFabric.Numerics/

--

--