# Generic math in .NET

## Before .NET 7

In C#, arithmetic operators are a handy feature that allows you to work with mathematical expressions using familiar symbols like `+`

, `-`

, `*`

, and `/`

. This simplifies your code by eliminating the need to call specific methods such as `Add`

, `Subtract`

, `Multiply`

, or `Divide`

for basic arithmetic operations.

For more complex math operations, especially those involving trigonometric functions, C# provides the `System.Math`

static class. It’s worth noting that most of the methods in this class work with double precision (double) values. However, in certain scenarios, using single precision (float) may offer better performance. To cater to this need, starting with .NET Core 2, there’s also a `System.MathF`

class that handles single precision operations.

While C# allows you to define custom arithmetic operators for your types, doing so with generics can be challenging. Generics are designed to work with known methods, and since operators are essentially static methods, they cannot be defined within an interface. This complexity makes defining arithmetic operators for generic types like `Vector<T>`

possible but cumbersome and challenging to maintain. As a result, many libraries opt to define `Vector`

types for specific numerical types to simplify the implementation.

## .NET 7 and beyond!

.NET 7, along with C# 11, introduces a range of exciting features that break previous limitations. One notable advancement is the ability to declare static virtual methods in interfaces, including those for arithmetic operators.

In the `System.Numerics`

namespace, you’ll find numerous interfaces that define sets of mathematical operations associated with native numerical .NET types. For instance, if a type implements `IAdditionOperators<TSelf, TOther, TResult>`

, it automatically has the operator + implemented.

This opens the door to implementing a generic Sum method as follows:

1
2
3
4
5
6
7
8

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 allows you to sum a collection of any type that defines the additive identity (zero) and the operator `+`

, which is used by the `+=`

operator. This not only covers all .NET native numerical types but can also extend to other types like vectors, quaternions, matrices, and more. It’s a versatile and powerful addition to the C# language.

You have the option to limit T to `INumber<T>`

, enabling the use of any numeric native type, or you can choose to restrict it to `IFloatingPoint<T>`

, thus permitting any floating-point native type. However, maintaining the method as is allows its use by types that may not fully implement all the interfaces mandated by `INumber<TSelf>`

or `IFloatingPoint<TSelf`

. These serve as the bare minimum requirements and will still accommodate a broad range of additional types.

In `System.Numerics`

, you’ll find new interfaces tailored for mathematical operations beyond basic arithmetic operators. One of these is `ITrigonometricFunctions<TSelf>`

, which is now implemented by certain native numerical types in .NET. This opens the door to the following usage:

1
2
3
4
5
6
7
8
9
10
11

// 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);

Observe that constants like `Pi`

, as well as the `Sin`

and `Asin`

methods, are now defined for the floating-point types `Half`

, `float`

, and `double`

. This makes it unnecessary and even discouraged to use `System.Math`

or `System.MathF`

. Furthermore, this enhancement expands the support for various numeric types.

`System.Numerics`

offers a range of similar interfaces, including `IExponentialFunctions<TSelf>`

, `IHyperbolicFunctions<TSelf>`

, `ILogarithmicFunctions<TSelf>`

, `IPowerFunctions<TSelf>`

, `IRootFunctions<TSelf>`

, and more.

Notably, the constants `E`

, `Pi`

, and `Tau`

are defined within the `IFloatingPointConstants<TSelf>`

interface.

## Generic Mathematics with Custom Types

When crafting your own types that involve operators, it’s beneficial to implement generic math interfaces. Here’s a streamlined representation of a 2D vector:

1
2
3
4
5
6
7
8
9
10
11

public readonly record struct MyVector2<T>(T X, T Y)
: IAdditiveIdentity<MyVector2<T>, MyVector2<T>>
, IAdditionOperators<MyVector2<T>, MyVector2<T>, MyVector2<T>>
where T : struct, INumber<T>
{
public static MyVector2<T> AdditiveIdentity
=> new(T.AdditiveIdentity, T.AdditiveIdentity);
public static MyVector2<T> operator +(MyVector2<T> left, MyVector2<T> right)
=> new(left.X + right.X, left.Y + right.Y);
}

This vector adheres to both the `IAdditiveIdentity<T, T>`

and `IAdditionOperators<T, T, T>`

interfaces. Utilizing `Sum()`

as illustrated above enables the calculation of the sum of a collection of vectors.

## Conclusions

Generics have expanded their utility, simplifying code and enhancing maintainability.

It’s recommended to transition away from `System.Math`

or `System.MathF`

and use the inherent math capabilities of each data type.

For those developing custom numeric types, implementing the interfaces provided in `System.Numerics`

is important to ensure compatibility with third-party methods.

As a side note, I’ve been actively working on an open-source library that uses .NET generic math to implement primitives across various coordinate systems, including rectangular 2D and 3D, polar, spherical, and geodetic. These implementations are designed as immutable value types, using generics to prevent unit mismatch execution errors. Feel free to explore it at https://netfabric.github.io/NetFabric.Numerics/