Generic Math Part 3: Making an Interface with Operators
This is the fourth post in a series about generic math.
We’ve managed to solve our problem with a generic method and the
IAdditionOperators<TSelf, TOther, TResult> interface, but now let’s take a peek at what else is available.
This isn’t a deep dive into every single interface, but will give us an overview.
IAdditionOperators, there are quite a few interfaces that define a single operator or a small group of closely related operators.
These are very useful and it is not hard to add them to your own types when adding operator overloads, if you have a desire to do so.
IAdditionOperators: Requires the
ISubtractionOperators: Requires the subtraction operator,
IMultiplyOperators: Requires the
IDivisionOperators: Requires the
IModulusOperators: Requires the remainder operator
IIncrementOperators: Requires the
IDecrementOperators: Requires the
IUnaryNegationOperators: Requires the unary negation operator,
-. (This is for things like
-a, contrasted with
a - b.)
IUnaryPlusOperators: Requires the unary plus operator,
+. (This is for things like
+a, contrasted with
a + b.)
IEqualityOperators: Requires the
IComparisonOperators: Requires the comparison operators,
IBitwiseOperators: Requires the bitwise operators,
IShiftOperators: Requires the bit shifting operators,
Static Method-Based Interfaces
There is a second pile of interfaces that don’t define operators, but, rather, define small collections of static methods.
An example of where these things could be useful is a method that needs to compute a square root.
Math class has a method for exactly this,
public static double Sqrt(double value), but that only works if you know ahead of time that you have a
MathF has a method for the
public static float Sqrt(float value). But again, you need to know ahead of time that you have a
While you often know exactly what you’ve got ahead of time, if you could define this in a generic way, it could make it easier to write code that needs to use
Sqrt but wants to be generic to handle, say, both
Indeed, this second group of interfaces is more about simple things like allowing a single generic algorithm to work for
decimal, or a single algorithm for
long instead of bringing in all sorts of crazy concepts like our
Point class, over the last few posts in this series.
So here’s the interfaces from this set:
IRootFunctions:This interface defines static methods for roots (
Sqrtfor square roots,
Cbrtfor cube roots,
RootNfor the nth root, etc.).
IPowerFunctions: This interface defines the static
IExponentialFunctions: This interface defines static methods for exponential operations like 10^x.
ILogarithmicFunctions: This interface defines static methods for logarithmic functions, such as
ITrigonometricFunctions:This interface defines static methods for the trigonometric functions like sine, cosine, tangent, and their inverses.
IHyperbolicFunctions: This interface defines static functions for the hyperbolic functions like
IFloatingPointConstants: This interface defines static, generic constants for the mathematical numbers E, Pi, and Tau.
IMinMaxValue: This interface defines static properties for
IAdditiveIdentity: This interface defines a static property for the type’s additive identity–a value that when added to other things leaves it unchanged. This is an analog to the number zero.
IMultiplicativeIdentity: This interface defines a static property for the type’s multiplicative identity–a value that when multiplied with other things leaves the thing unchanged. This is analog to the number one.
You probably won’t implement these interfaces yourself, but the different built-in types support these as makes sense.
On the other hand, if you were building out something that was truly a representation of a number, such as a
Fraction type that represents fractional values like 3/4 without turning them into a floating-point number, you may very well find yourself implementing all of these.
The Aggregation Interfaces
The previous two bundles of interfaces are very granular. Everything is as small as it can reasonably be and still make sense. But the interfaces that follow define much larger concepts. These interfaces extend many of the interfaces above, and some add additional required static members as well.
INumberBase<T>is the simplest, but it is not simple at all. It represents a foundational interface that covers most numeric operations, inluding
INumber<T>represents an arbitrary number of any type, building upon
INumberBase<T>, and adds the comparison operators,
IUnsignedNumber<T>represents unsigned numbers. It augments
INumber<T>. This is a useful type if you want to restrict things to definitely being unsigned and incapable of representing negative numbers.
ISignedNumber<T>represents signed numbers. It augments
INumber<T>. This is useful if you want to ensure something is a signed number and can definitely represent at least some negative numbers (depending on its range).
IFloatingPoint<T>. This represents a floating-point number, so using it will guarantee that you have somethign that can represent non-integers.
IBinaryNumber<T>. This represents a number with a binary representation. This is a bit misleading, because there really isn’t a way to represent anything on the computer without it being binary. But this is what you might use if you want to work with the individual bits that compose a number. This builds on
INumber<T>, but also adds in the shift and bitwise operators for bit manipulation.
IBinaryInteger<T>. This represents a number that can be manipulated at the bit level but that is also known to be an integer, incapable of storing fractional/decimal values.
IFloatingPointIeee754<T>. This awkward name is an interface that augments
IFloatingPoint<T>but that makes promises that it works in accordance with the IEEE (“eye triple ee”) 754 standard, which includes things like a concept of
IBinaryFloatingPointIeee754. This is an IEEE 754-compliant floating point number with a binary representation.
doubleboth implement this.
The sheer number of members that you need to add to implement even the humble
INumberBase<T> is quite a bit.
It likely isn’t worth going to that trouble unless you’re building a key numeric type that you want to use widely, and especially one you want to be able to swap in different algorithms for the other numeric types like
But if you are doing that, you can climb as far up this interface tree as you need.
Fhew! That was a lot. And we didn’t cover all the details. You can go explore these interfaces in the documentation on learn.microsoft.com.