.NET 7 Preview 5 – Generic Math
In .NET 6 we previewed a feature known as Generic Math. Since then, we have made continuous improvements to the implementation and responded to various feedback from the community in order to ensure that relevant scenarios are possible and the necessary APIs are available.
If you missed out on the original blog post, Generic Math combines the power of generics and a new feature known as static virtuals in interfaces
to allow .NET developers to take advantage of static APIs, including operators, from generic code. This means that you get all the power of generics, but now with the ability to constrain the input to number like types, so you no longer need to write or maintain many near identical implementations just to support multiple types. It also means that you get access to all your favorite operators and can use them from generic contexts. That is, you can now have static T Add<T>(T left, T right) where T : INumber<T> => left + right;
whereas previously it would have been impossible to define.
Much like generics, this feature will see the most benefits by API authors where they can simplify the amount of code required they need to maintain. The .NET Libraries did just this to simplify the Enumerable.Min
and Enumerable.Max
APIs exposed as part of LINQ. Other developers will benefit indirectly as the APIs they consume may start supporting more types without the requirement for each and every numeric type to get explicit support. Once an API supports INumber<T>
then it should work with any type that implements the required interface. All devs will likewise benefit from having a more consistent API surface and having more functionality available by default. For example, all types that implement IBinaryInteger<T>
will support operations like +
(Addition), 
(Subtraction), <<
(Left Shift), and LeadingZeroCount
.
Generic Math
Lets take a look at an example piece of code that computes a standard deviation. For those unfamiliar, this is a math function used in statistics that builds on two simpler methods: Sum
and Average
. It is basically used to determine how spread apart a set of values are.
The first method we’ll look at is Sum
, which just adds a set of values together. The method takes in an IEnumerable<T>
where T
must be a type that implements the INumber<T>
interface. It returns a TResult
with a similar constraint (it must be a type that implements INumber<TResult>
). Because two generic parameters are here, it is allowed to return a different type than it takes as an input. This means, for example, you can do Sum<int, long>
which would allow summing the values of an int[]
and returning a 64bit result to help avoid overflow. TResult.Zero
efficiently gives the value of 0
as a TResult
and TResult.CreateChecked
converts value
from a T
into a TResult
throwing an OverflowException
if it is too large or too small to fit in the destination format. This means, for example, that Sum<int, byte>
would throw if one of the input values was negative or greater than 255
.
public static TResult Sum<T, TResult>(IEnumerable<T> values)
where T : INumber<T>
where TResult : INumber<TResult>
{
TResult result = TResult.Zero;
foreach (var value in values)
{
result += TResult.CreateChecked(value);
}
return result;
}
The next method is Average
, which just adds a set of values together (calls Sum
) and then divides that by the number of values. It doesn’t introduce any additional concepts beyond what were used in Sum
. It does show use of the division operator.
public static TResult Average<T, TResult>(IEnumerable<T> values)
where T : INumber<T>
where TResult : INumber<TResult>
{
TResult sum = Sum<T, TResult>(values);
return TResult.CreateChecked(sum) / TResult.CreateChecked(values.Count());
}
StandardDeviation
is the last method, as indicated above it basically determines how far apart a set of values are. For example, { 0, 50, 100 }
has a high deviation of 49.501
; { 0, 5, 10 }
on the other hand has a much lower deviation of just 4.5092
. This method introduces a different constraint of IFloatingPointIeee754
which indicates the return type must be an IEEE 754
floatingpoint type such as double
(System.Double
) or float
(System.Single
). It introduces a new API CreateSaturating
which explicitly saturates, or clamps, the value on overflow. That is, for byte.CreateSaturating<int>(value)
it would convert 1
to 0
because 1
is less than the minimum value of 0
. It would likewise convert 256
to 255
because 256
is greater than the maximum value of 255
. Saturation is the default behavior for IEEE 754
floatingpoint types as they can represent positive and negative infinity as their respective minimum and maximum values. The only other new API is Sqrt
which behaves just like Math.Sqrt
or MathF.Sqrt
and calculates the square root
of the floatingpoint value.
public static TResult StandardDeviation<T, TResult>(IEnumerable<T> values)
where T : INumber<T>
where TResult : IFloatingPointIeee754<TResult>
{
TResult standardDeviation = TResult.Zero;
if (values.Any())
{
TResult average = Average<T, TResult>(values);
TResult sum = Sum<TResult, TResult>(values.Select((value) => {
var deviation = TResult.CreateSaturating(value)  average;
return deviation * deviation;
}));
standardDeviation = TResult.Sqrt(sum / TResult.CreateSaturating(values.Count()  1));
}
return standardDeviation;
}
These methods can then be used with any type that implements the required interfaces and in .NET 7 preview 5 we have 20 types that implement these interfaces out of the box. The following table gives a brief description of those types, the corresponding language keyword for C# and F# when that exists, and the primary generic math interfaces they implement. More details on these interfaces and why they exist are provided later on in the Available APIs section.
.NET Type Name  C# Keyword  F# Keyword  Implemented Generic Math Interfaces 

System.Byte  byte  byte  IBinaryInteger, IMinMaxValue, IUnsignedNumber 
System.Char  char  char  IBinaryInteger, IMinMaxValue, IUnsignedNumber 
System.Decimal  decimal  decimal  IFloatingPoint, IMinMaxValue 
System.Double  double  float, double  IBinaryFloatingPointIeee754, IMinMaxValue 
System.Half  IBinaryFloatingPointIeee754, IMinMaxValue  
System.Int16  short  int16  IBinaryInteger, IMinMaxValue, ISignedNumber 
System.Int32  int  int  IBinaryInteger, IMinMaxValue, ISignedNumber 
System.Int64  long  int64  IBinaryInteger, IMinMaxValue, ISignedNumber 
System.Int128  IBinaryInteger, IMinMaxValue, ISignedNumber  
System.IntPtr  nint  nativeint  IBinaryInteger, IMinMaxValue, ISignedNumber 
System.Numerics.BigInteger  IBinaryInteger, IUnsignedNumber  
System.Numerics.Complex  INumberBase, ISignedNumber  
System.Runtime.InteropServices.NFloat  IBinaryFloatingPointIeee754, IMinMaxValue  
System.SByte  sbyte  sbyte  IBinaryInteger, IMinMaxValue, ISignedNumber 
System.Single  float  float32, single  IBinaryFloatingPointIeee754, IMinMaxValue 
System.UInt16  ushort  uint16  IBinaryInteger, IMinMaxValue, IUnsignedNumber 
System.UInt32  uint  uint  IBinaryInteger, IMinMaxValue, IUnsignedNumber 
System.UInt64  ulong  uint64  IBinaryInteger, IMinMaxValue, IUnsignedNumber 
System.UInt128  IBinaryInteger, IMinMaxValue, IUnsignedNumber  
System.UIntPtr  nuint  unativeint  IBinaryInteger, IMinMaxValue, IUnsignedNumber 
This means that out of the box users get a broad set of support for Generic Math. As the community adopts these interfaces for their own types, the support will continue to grow.
Types Without Language Support
Readers might note that there are a few types here that don’t have an entry in the C# Keyword
or F# Keyword
column. While these types exist and are supported fully in the BCL, languages like C# and F# do not provide any additional support for them today and so users may surprised when certain language features do not work with them. Some examples are that the language won’t provide support for literals (Int128 value = 0xF_FFFF_FFFF_FFFF_FFFF
isn’t valid), constants (const Int128 Value = 0;
isn’t valid), constant folding (Int128 value = 5;
is evaluated at runtime, not at compile time), or various other functionality that is limited to types that have corresponding language keywords.
The types without language support are:
System.Half
is a 16bit binary floatingpoint type that implements the IEEE 754 standard much likeSystem.Double
andSystem.Single
. It was originally introduced in .NET 5System.Numerics.BigInteger
is an arbitrary precision integer type and automatically grows to fit the value represented. It was originally introduced in .NET Framework 4.0System.Numerics.Complex
can represent the expressiona + bi
wherea
andb
areSystem.Double
andi
is the imaginary unit. It was originally introduced in .NET Framework 4.0System.Runtime.InteropServices.NFloat
is a variable precision binary floatingpoint type that implements the IEEE 754 standard and much likeSystem.IntPtr
it is 32bits on a 32bit platform (equivalent toSystem.Single
) and 64bits on a 64bit platform (equivalent toSystem.Double
) It was originally introduced in .NET 6 and is primarily meant for interop purposes.System.Int128
is a 128bit signed integer type. It is new in .NET 7System.UInt128
is a 128bit unsigned integer type. It is new in .NET 7
Breaking Changes Since .NET 6
The feature that went out in .NET 6 was a preview and as such there have been several changes to the API surface based on community feedback. This includes, but is not limited to:
 Renaming
System.IParseable
toSystem.IParsable
 Moving all other new numeric interfaces to the
System.Numerics
namespace  Introducing
INumberBase
so that types likeSystem.Numerics.Complex
can be represented  Splitting the IEEE 754 specific APIs into their own
IFloatingPointIeee754
interface so types likeSystem.Decimal
can be represented  Moving various APIs lower in the type hierarchy such as the
IsNaN
orMaxNumber
APIs Many of the concepts will return a constant value or be a
noop
on various type  Despite this, it is still important that they’re available, since the exact type of a generic is unknown and many of these concepts are important for more general algorithms
 Many of the concepts will return a constant value or be a
.NET API reviews are done in the open and are livestreamed for all to view and participate in. Past API review videos can be found on our YouTube channel.
The design doc for the Generic Math feature is available in the dotnet/designs repo on GitHub.
The corresponding PRs updating the document, general discussions around the feature, and links back to the relevant API reviews are also available.
Support in other languages
F# is getting support for static virtuals in interfaces as well and more details should be expected soon in the fsharp/fslangdesign repo on GitHub.
A fairly 1to1 translation of the C# Sum
method using the proposed F# syntax is expected to be:
let Sum<'T, 'TResult when 'T :> INumber<'T> and 'TResult :> INumber<'TResult>>(values : IEnumerable<'T>) =
let mutable result = 'TResult.Zero
for value in values do
result < result 'TResult.CreateChecked(value)
result
Available APIs
Numbers and math are both fairly complex topics and the depth in which one can go is almost without limit. In programming there is often only a loose mapping to the math one may have learned in school and special rules or considerations may exist since execution happens in a system with limited resources. Languages therefore expose many operations that make sense only in the context of certain kinds of numbers or which exist primarily as a performance optimization due to how hardware actually works. The types they expose often have welldefined limits, an explicit layout of the data they are represented by, differing behaviors around rounding or conversions, and more.
Because of this there remains a need to both support numbers in the abstract sense while also still supporting programming specific constructs such as floatingpoint vs integer, overflow, unrepresentable results; and so it was important as part of designing this feature that the interfaces exposed be both finegrained enough that users could define their own interfaces built on top while also being granular enough that they were easy to consume. To that extent, there are a few core numeric interfaces that most users will interact with such as System.Numerics.INumber
and System.Numerics.IBinaryInteger
; there are then many more interfaces that support these types and support developers defining their own numeric interfaces for their domain such as IAdditionOperators
and ITrigonometricFunctions
.
Which interfaces get used will be dependent on the needs of the declaring API and what functionality it relies on. There are a range of powerful APIs exposed to help users efficiently understand the value they’ve been and decide the appropriate way to work with it including handling edge cases (such as negatives, NaNs, infinities, or imaginary values), having correct conversions (including throwing, saturating, or truncating on overflow), and being extensible enough to version the interfaces moving forward by utilizing Default Interface Methods.
Numeric Interfaces
The types most users will interact with are the numeric interfaces
. These define the core interfaces describing numberlike types and the functionality available to them.
Interface Name  Summary 

System.Numerics.IAdditiveIdentity  Exposes the concept of (x + T.AdditiveIdentity) == x 
System.Numerics.IMinMaxValue  Exposes the concept of T.MinValue and T.MaxValue (types like BigInteger have no Min/MaxValue) 
System.Numerics.IMultiplicativeIdentity  Exposes the concept of (x * T.MultiplicativeIdentity) == x 
System.Numerics.IBinaryFloatingPointIeee754  Exposes APIs common to binary floatingpoint types that implement the IEEE 754 standard 
System.Numerics.IBinaryInteger  Exposes APIs common to binary integers 
System.Numerics.IBinaryNumber  Exposes APIs common to binary numbers 
System.Numerics.IFloatingPoint  Exposes APIs common to floatingpoint types 
System.Numerics.IFloatingPointIeee754  Exposes APIs common to floatingpoint types that implement the IEEE 754 standard 
System.Numerics.INumber  Exposes APIs common to comparable number types (effectively the “Real” number domain) 
System.Numerics.INumberBase  Exposes APIs common to all number types (effectively the “Complex” number domain) 
System.Numerics.ISignedNumber  Exposes APIs common to all signed number types (such as the concept of NegativeOne ) 
System.Numerics.IUnsignedNumber  Exposes APIs common to all unsigned number types 
While there are a few different types here, most users will likely work directly with INumber<TSelf>
. This roughly corresponds to what some users may recognize as a “real” number and means the value has a sign and welldefined order, making it IComparable
. INumberBase<TSelf>
convers more advanced concepts including “complex” and “imaginary” numbers.
Most of the other interfaces, such as IBinaryNumber
, IFloatingPoint
, and IBinaryInteger
, exist because not all operations make sense for all numbers. That is, there are places where APIs only makes sense for values that are known to be binarybased and other places where APIs only make sense for floatingpoint types. The IAdditiveIdentity
, IMinMaxValue
, and IMultiplicativeIdentity
interfaces exist to cover core properties of number like types. For IMinMaxValue
in particular, it exists to allow access to the upper (MaxValue
) and lower (MinValue
) bounds of a type. Certain types like System.Numerics.BigInteger
may not have such bounds and therefore do not implement this interface.
IFloatingPoint<TSelf>
exists to cover both IEEE 754
types such as System.Double
, System.Half
, and System.Single
as well as other types such as System.Decimal
. The number of APIs provided by it is much lesser and it is expected most users who explicitly need a floatingpointlike type will use IFloatingPointIeee754
. There is not currently any interface to describe “fixedpoint” types but such a definition could exist in the future if there is enough demand.
These interfaces expose APIs previously only available in System.Math
, System.MathF
, and System.Numerics.BitOperations
. This means that functions like T.Sqrt(value)
are now available to anything implementing IFloatingPointIeee754<T>
(or more specifically the IRootFunctions<T>
interface covered below).
Some of the core APIs exposed by each interface includes, but is not limited to the below.
Interface Name  API Name  Summary 

IBinaryInteger  DivRem  Computes the quotient and remainder simultaneously 
LeadingZeroCount  Counts the number of leading zero bits in the binary representation  
PopCount  Counts the number of set bits in the binary representation  
RotateLeft  Rotates bits left, sometimes also called a circular left shift  
RotateRight  Rotates bits right, sometimes also called a circular right shift  
TrailingZeroCount  Counts the number of trailing zero bits in the binary representation  
IFloatingPoint  Ceiling  Rounds the value towards positive infinity. +4.5 becomes +5, 4.5 becomes 4 
Floor  Rounds the value towards negative infinity. +4.5 becomes +4, 4.5 becomes 5  
Round  Rounds the value using the specified rounding mode.  
Truncate  Rounds the value towards zero. +4.5 becomes +4, 4.5 becomes 4  
IFloatingPointIeee754  E  Gets a value representing Euler’s number for the type 
Epsilon  Gets the smallest representable value that is greater than zero for the type  
NaN  Gets a value representing NaN for the type  
NegativeInfinity  Gets a value representing Infinity for the type  
NegativeZero  Gets a value representing Zero for the type  
Pi  Gets a value representing +Pi for the type  
PositiveInfinity  Gets a value representing +Infinity for the type  
Tau  Gets a value representing +Tau, or 2 * Pi for the type 

–Other–  –Implements the full set of interfaces defined under FunctionsÂ below–  
INumber  Clamp  Restricts a value to no more and no less than the specified min and max value 
CopySign  Sets the sign of a give value to the same as another specified value  
Max  Returns the greater of two values, returning NaN if either input is NaN  
MaxNumber  Returns the greater of two values, returning the number if one input is NaN  
Min  Returns the lesser of two values, returning NaN if either input is NaN  
MinNumber  Returns the lesser of two values, returning the number if one input is NaN  
Sign  Returns 1 for negative values, 0 for zero, and +1 for positive values  
INumberBase  One  Gets the value 1 for the type 
Radix  Gets the radix, or base, for the type. Int32 returns 2. Decimal returns 10  
Zero  Gets the value 0 for the type 

CreateChecked  Creates a value from another value, throwing if the other value can’t be represented  
CreateSaturating  Creates a value from another value, saturating if the other value can’t be represented  
CreateTruncating  Creates a value from another value, truncating if the other value can’t be represented  
IsComplexNumber  Returns true if the value has a nonzero real part and a nonzero imaginary part  
IsEvenInteger  Returns true if the value is an even integer. 2.0 returns true, 2.2 returns false  
IsFinite  Returns true if the value is not infinite and not NaN.  
IsImaginaryNumber  Returns true if the value has a zero real part. This means 0 is imaginary and 1 + 1i is not 

IsInfinity  Returns true if the value represents infinity.  
IsInteger  Returns true if the value is an integer. 2.0 and 3.0 return true, 2.2 and 3.1 return false  
IsNaN  Returns true if the value represents NaN  
IsNegative  Returns true if the value is negative, this includes 0.0  
IsPositive  Returns true if the value is positive, this includes 0 and +0.0  
IsRealNumber  Returns true if the value has a zero imaginary part. This means 0 is real as are all INumber<T> types 

IsZero  Returns true if the value represents zero, this includes 0, +0.0, and 0.0  
MaxMagnitude  Returns the value with a greater absolute value, returning NaN if either input is NaN  
MaxMagnitudeNumber  Returns the value with a greater absolute value, returning the number if one input is NaN  
MinMagnitude  Returns the value with a lesser absolute value, returning NaN if either input is NaN  
MinMagnitudeNumber  Returns the value with a lesser absolute value, returning the number if one input is NaN  
ISignedNumber  NegativeOne  Gets the value 1 for the type 
Functions
The function interfaces define common mathematical APIs that may be more broadly applicable than to a specific numeric interface. They are currently all implemented by IFloatingPointIeee754
and may also get implemented by other relevant types in the future.
Interface Name  Summary 

System.Numerics.IExponentialFunctions  Exposes exponential functions supporting e^x , e^x  1 , 2^x , 2^x  1 , 10^x , and 10^x  1 
System.Numerics.IHyperbolicFunctions  Exposes hyperbolic functions supporting acosh(x) , asinh(x) , atanh(x) , cosh(x) , sinh(x) , and tanh(x) 
System.Numerics.ILogarithmicFunctions  Exposes logarithmic functions supporting ln(x) , ln(x + 1) , log2(x) , log2(x + 1) , log10(x) , and log10(x + 1) 
System.Numerics.IPowerFunctions  Exposes power functions supporting x^y 
System.Numerics.IRootFunctions  Exposes root functions supporting cbrt(x) and sqrt(x) 
System.Numerics.ITrigonometricFunctions  Exposes trigonometric functions supporting acos(x) , asin(x) , atan(x) , cos(x) , sin(x) , and tan(x) 
Parsing and Formatting
Parsing and formatting are core concepts in programming. They are typically used to support converting user input to a given type or to display a given type to the user.
Interface Name  Summary 

System.IFormattable  Exposes support for value.ToString(string, IFormatProvider) 
System.ISpanFormattable  Exposes support for value.TryFormat(Span<char>, out int, ReadOnlySpan<char>, IFormatProvider) 
System.IParsable  Exposes support for T.Parse(string, IFormatProvider) 
System.ISpanParsable  Exposes support for T.Parse(ReadOnlySpan<char>, IFormatProvider) 
Operators
Central to Generic Math is the ability to expose operators as part of an interface. .NET 7 provides the following interfaces which expose the core operators supported by most languages. This also includes new functionality in the form of userdefined checked operators
and unsigned right shift
.
Interface Name  Summary 

System.Numerics.IAdditionOperators  Exposes the x + y and checked(x + y) operators 
System.Numerics.IBitwiseOperators  Exposes the x & y , x  y , x ^ y , and ~x operators 
System.Numerics.IComparisonOperators  Exposes the x < y , X > y , x <= y , and x >= y operators 
System.Numerics.IDecrementOperators  Exposes the x , checked(x) , x , and checked(x) operators 
System.Numerics.IDivisionOperators  Exposes the x / y and checked(x / y) operators 
System.Numerics.IEqualityOperators  Exposes the x == y and x != y operators 
System.Numerics.IIncrementOperators  Exposes the ++x , checked(++x) , x++ , and checked(x++) operators 
System.Numerics.IModulusOperators  Exposes the x % y operator 
System.Numerics.IMultiplyOperators  Exposes the x * y and checked(x * y) operators 
System.Numerics.IShiftOperators  Exposes the x << y , x >> y , and x >>> y operators 
System.Numerics.ISubtractionOperators  Exposes the x  y and checked(x  y) operators 
System.Numerics.IUnaryNegationOperators  Exposes the x and checked(x) operators 
System.Numerics.IUnaryPlusOperators  Exposes the +x operator 
UserDefined Checked Operators
Userdefined checked operators
allow a different implementation to be provided which will throw System.OverflowException
rather than silently truncating their result. These alternative implementations are available to C# code by using the checked
keyword or setting <CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
in your project settings. The versions that truncate are available by using the unchecked
keyword or ensuring CheckForOverflowUnderflow
is false
(this is the default experience for new projects).
Some types, such as floatingpoint types, may not have differing behavior as they saturate
to PositiveInfinity
and NegativeInfinity
rather than truncating. BigInteger
is another type that does not have differing behavior between the unchecked and checked versions of the operators as the type simply grows to fit the value. 3rd party types may also have their own unique behavior.
Developers can declare their own userdefined checked operators
by placing the checked
keyword after the operator
keyword. For example, public static Int128 operator checked +(Int128 left, Int128 right)
declares a checked addition
operator and public static explicit operator checked int(Int128 value)
declares a checked explicit conversion
operator.
Unsigned Right Shift
Unsigned right shift (>>>
) allows shifting to occur that doesn’t carry the sign. That is, for 8 >> 2
the result is 2
while 8 >>> 2
is +1073741822
.
This is somewhat easier to visualize when looking at the hexadecimal or binary representation. For x >> y
the sign of the value is preserved and so for positive values 0
is shifted in while for negative values 1
is shifted in instead. However, for x >>> y
the sign of the value is ignored and 0
is always shifted in. This is similar to first casting the value to an unsigned
type of the same sign and then doing the shift, that is it is similar to (int)((uint)x >> y)
for int
.
Expression  Decimal  Hexadecimal  Binary 

8 
8  0xFFFF_FFF8  0b1111_1111_1111_1111_1111_1111_1111_1000 
8 >> 2 
2  0xFFFF_FFFE  0b1111_1111_1111_1111_1111_1111_1111_1110 
8 >>> 2 
+1,073,741,822  0x3FFF_FFFE  0b0011_1111_1111_1111_1111_1111_1111_1110 
Closing
The amount of functionality now available in a generic context is quite large, allowing your code to be simpler, more maintainable, and more expressive. Generic Math will empower every developer to achieve more, and we are excited to see how you decide to utilize it!
34 comments
Too bad preview 5 is not (yet) available for download.
It will be great for libraries like MathDotNet or AlgLib. Too much duplicated code, right now, and many contrived techniques to support all required types. It’s a big step forward!
Wait a minute…
If T and TResult are integers, sum / count will give integer value, isn’t it? It’s not what would be expected for avg of [3, 4].
P.S. Sorry, some code parts were “eaten”
Right. But that’s just how integers work and sometimes that’s appropriate.
For integers, Division is generally a lossy operation and so a dev can decide if that’s ok for them or not. In the case of
StandardDeviation
, we needSqrt
which is only available to floatingpoint values and so theAverage
it calls will likewise return a floatingpoint result, even if the inputs are integers.One could also restrict
TResult : IFloatingPoint<TResult>
(same goes forT
or any other constraint) if that was more appropriate for their library.Why would you write any kind of number crunchy math library that virtualizes ever single operation?
Holy performance Batman!
For value types, these won’t be virtual calls as the JIT supports generic specialization for value types. This means each unique
T
gets its own version of the method compiled and includes eliminating dead code paths (includingif (typeof(T) == typeof(...))
checks), doing struct promotion, enregistration, constant folding, and other optimizations including removing boxing where possible.For reference types, there may be a minimal type of dynamic dispatch, but since none of the core numeric types are structs and since that likewise applies to the most widely used third party numeric types as well, this won’t impact most users. This dynamic dispatch will also go away entirely for cases where the type can be statically devirtualized (such as when inlined and the caller was using a concrete type). In the future this cost may be further reduced with PGO, guarded devirtualization, or other compiler optimization tricks.
The following sharplab link shows an example of how a seemingly complex piece of code is optimized down to just the relevant code path for
int
:https://sharplab.io/#v2:EYLgxg9gTgpgtADwGwBYA0AXEBDAzgWwB8ABAJgEYBYAKGIAYACY8gOgCUBXAOwwEt8YLAMIR8AB14AbGFADKMgG68wMXAG4aNYgGYmpBkIYBvGgzMMA2rIAW2KGIAy2YO258BLAFK8MAcRhcMsoAFBgAnmIwEABmwbw8AJQJALoMAPRpDAAq1ry4DHkMEFySYQyw0grYPAwYEAy4tvaSzgzR0AwAJnliLWHxAOZdeXi4MPjApabmFgCyMBjWEJ0AkuKSwfOLy2u9APJifMW4LACCAwOwuLi8CjArJfGDKdNmOkzkSNkMp52dADxZAB8wSyDGk0QwaG+UF4A2sGASr2MyPMBWiDFCESisSyCQYAF4CbVsTFgsAwhgYElUeYTNQ0YzzMQAOyYvHBCDAABWMDAiPJlOpwUFVISnJ5fMREIwDAA1JiKWKJbz+QlYfDERoGUyGABfWlmGCSMbozHhSJkvGE4kWnHBToQDiTalInVM+m6xms9nirmqgWO53ScUOp0uv2StUy+WYoMRlVS9VwhEJbVeg3uxnG028DFYy24/FEkmF4KNaCIt1ez1e5ls0GRgPiitQAXlpZtptJmMKjuV7tqjWp9O6zNenMwM0F+3Wkt2snxKuGlFZr0+xuJtUipeD6UwSGxuKJLeI4dalfj3WT6cLos20v2yTFAY0td0ldojcc/1JkXPrhX1PBJe0xACgN/IcUwvd8zCvJkbzzc1SXvecUOCLhd2rXVazrJgGx/KN20wk9IP3Q8+xIgUyOTTU00vFdEPzO9GwfFiuA4LCV1wutvz3UMON3YDQIwzjSKI2iRwY2CGCY5CyznW10NwJVXW4z9619YDy1U0MVKFfiRP05UaPPeiZPg7MTSnJCZytYslLLaJn2wZcZJ49cCP44JnIgVzQ18/zhIPWU+0C6iJLM0cmUstE5Ls1DHPtDhWzcmsNLeLztJSzt2xygdgoozF8q7YCoukidrNvdDFMfMkxLSnCMvwrSaJFBrDJCo8OrK6DzIzRiqtsljapYjhwLfdKZK/LK2uCcaX06or5om3q6OixlYvMSd1Om8xFigCAAHcGECE6ADkIAwWQODEMRKxgToAFEEBUQ5eGKYJ+rHZFMz1IA=
Cannot use triple shift operator, compiler report invalid syntax. Even using langversion preview + enable preview features + experimental package………..
screenshot: https://i.imgur.com/j8E8q97.png
csproj: https://i.imgur.com/oSxlFLt.png
Unable to build.
You’ll need at least .NET 7 Preview 4 to have access to
unsigned right shift
anduserdefined checked operators
.For .NET 7 Preview 3 and later, you no longer need the
System.Runtime.Experimental
package as the relevant bits are now inbox and available by default.Many of the changes discussed in the blog post here are in .NET 7 Preview 4, a few of them are only available on .NET 7 Preview 5 which isn’t quite out yet. — For reference, this blog post got moved up to align with the .NET Community Standup stream covering Generic Math (https://dotnet.microsoft.com/enus/live/communitystandup). .NET 7 Preview 5 will be out “soon”.
Provided you have at least .NET 7 Preview 4,
unsigned right shift
(this also applies to `userdefined checked operators` and the `Generic Math` feature more generally) can be utilized simply via:and then for the
Program.cs
:I have to agree that the need of a BigDecimal is something that should be on the radar, but I love the addition of uint128 and int128. To be honest .NET 7 is looking great, lots of great additions along with C#11. I’m exited to see all of this when the next LTS arrives and all the new stuff that will come with it!
Is implicit `TSelf` (like in rust) yet possible?
T: INumber<T>
semantically makes no sense, and is logically improper.The language doesn’t currently have any support for a self type. It is something that might be considered in the future and they will consider if it can be done in a way that can be extended to existing types (like
INumber<T>
) in a binary compatible way. There is no guarantee that will happen, however, and it may require entirely new/incompatible representation.where T : SomeType<T>
is actually a very common and welldefined pattern throughout programming. It’s known as the “Curiously Recurring Template Pattern” (CRTP) and it even has an in depth wiki page covering it. CRTP simply means that the parameterT
must implement the interfaceSomeType<T>
and therefore meansstruct Int32 : INumber<Int32>
is valid, butstruct Int32 : INumber<Guid>
is not.It’s worth noting there is a a small “hole” with CRTP in that this means that
struct Int32 : INumber<Int64>
would be technically valid to declare sinceInt64
implementsINumber<Int64>
. However, you shouldn’t do this since it will run into downstream usability issues in practice. You should always ensure the generic type matches the implementing type.Likewise since it isn’t a proper selftype,
TSelf x = this
and others syntax that would require a proper self constraint aren’t valid. This isn’t going to be hugely restrictive but it will impact some scenarios more than others.Maybe I am missing something but I believed this could would be possible:
Instead of:
Abs
is astatic
method and so it is only accessible asT.Abs(tValue)
and only whenT
is appropriately constrained (where T : INumber<T>
).Not being able to do something like
if (tValue is INumber<T>)
and then have access to the static members onT
is a known scenario that doesn’t work and is due to both time constraints and there not being existing support for such a scenario in the runtime. Basically, there is no way to bridge generic constraints here and so you can’t go from something that is justT
to calling something that iswhere T : unmanaged
for example.This is a scenario that will be looked at more in the future so that it can be handled appropriately.
For the time being, you’ll have to do something like:
Hi, where can i find c# 10 documentation like classes and member function, i want to learn to program in c# and i need to know the existing classes.
The new INumberbased interfaces are great. I tested this from both sides.
But I want to suggest some improvements – before it is to late.
The
INumberBase
function set should be improved.The set of 17
Is...
functions,IsCanonical(TSelf)
…IsZero(TSelf)
sould be one function like:NumberInfoFlags GetNumberInfo(TSelf, NumberInfoFlags mask)
* Easy extensible and compatible for future use by simply extend
enum NumberInfoFlags
and not by extend the interface.* Faster and easier to get the necessary information in templates with one hit to select the best possible alg. based on the flags.
* More type specific without more code, for all thinkable types like arbitrary, rational, vectors etc. where more infos is needed.
* Less code, more easy to implement on number side as the most flags are const and for others, using the mask param, to bypass unrequested, unnecessary calculations.
*
INumberBase<TSelf>.Radix
should be integrated here, as this can also be dynamic (and it’s always annoying to see this obvious information in debug) and what is Radix for rational?* Better for inline in most cases. And, for any reason it works better with inline that way (runtime/issues/68890) what I tested on X64.
The same applies to the functions
Create...<TOther>(TOther)
,TryConvert...<TOther>(TOther)
,TryConvertFrom...<TOther>(TOther)
* should be one function with enum parameter for the same reasons.
* I’ve seen in other implementations, I’ve done this in mine, on the number side a mapping to such a private general function(s) and it wouldn’t be necessary that way.
For the statics
TSelf One
,TSelf Zero
, There should be a function like:TSlef GetConst(NumberConst)
* easy extendable by extend
enum NumberConst
* can contain
MinValue
,MaxValue
,Epsilon
, etc.* can contain the best type specific representation for
Pi
,E
,Tau
etc. which is really importand for alg’s. as template.* can contain the props
AdditiveIdentity
,MultiplicativeIdentity
, etc. and would make approriated interfacesIAdditiveIdentity
,IMultiplicativeIdentity
obsolete.* The jump table in implementations really isn’t the problem and since everything is constant the compiler in release can inline this perfectly.
The
..Magnitude
functions should be moved in another interface.The
..TryFormat
functions should be moved in another interface.Also for the other interfaces. They could all be improved upon and benefit from this approach by using enum parameters for capability checks and then only one static method is required for each method body type, unary, binary etc.
Consistently thought: the entire current
INumber
interface set could be implemented in one interface and everything would be more flexible, more typespecific, extensible for the future, less code, easier to read, better inline, more performance.But yes, it is also nice to see the always same normed function names for similar types and to force this by the interfaces.