Generische Mathematik
In .NET 7 werden neue mathematikbezogene generische Schnittstellen für die Basisklassenbibliothek eingeführt. Dank dieser Schnittstellen können Sie einen Typparameter eines generischen Typs oder einer generischen Methode so beschränken, dass er „zahlenähnlich“ ist. Darüber hinaus können Sie ab C# 11 Schnittstellenmember vom Typ static virtual
definieren. Da Operatoren als static
deklariert werden müssen, können mit diesem neuen C#-Feature Operatoren in den neuen Schnittstellen für zahlenähnliche Typen deklariert werden.
Durch die Kombination dieser Innovationen können mathematische Operationen generisch (also ohne Kenntnis des genauen Typs, mit dem Sie arbeiten) ausgeführt werden. Wenn Sie beispielsweise eine Methode schreiben möchten, die zwei Zahlen addiert, mussten Sie bislang für jeden Typ eine Überladung der Methode hinzufügen (beispielsweise static int Add(int first, int second)
und static float Add(float first, float second)
). Jetzt können Sie eine einzelne generische Methode schreiben, bei der der Typparameter auf einen zahlenähnlichen Typ beschränkt ist. Beispiel:
static T Add<T>(T left, T right)
where T : INumber<T>
{
return left + right;
}
In dieser Methode ist der Typparameter T
auf einen Typ beschränkt, der die neue Schnittstelle INumber<TSelf> implementiert. INumber<TSelf> implementiert die Schnittstelle IAdditionOperators<TSelf,TOther,TResult>, die den +-Operator enthält. Dadurch kann die Methode die beiden Zahlen generisch addieren. Die Methode kann mit jedem der integrierten numerischen Typen von .NET verwendet werden, da sie in .NET 7 alle für die Implementierung von INumber<TSelf> aktualisiert wurden.
Bibliotheksautor*innen profitieren am meisten von den generischen mathematischen Schnittstellen, da sie ihre Codebasis vereinfachen können, indem sie redundante Überladungen entfernen. Andere Entwickler*innen profitieren indirekt, da die von ihnen genutzten APIs künftig ggf. mehr Typen unterstützen.
Die Schnittstellen
Die Schnittstellen sind differenziert genug, dass Benutzer*innen auf ihrer Grundlage eigene Schnittstellen definieren können, gleichzeitig aber auch so präzise, dass sie problemlos genutzt werden können. Es gibt einige wichtige numerische Schnittstellen, mit denen die meisten Benutzer interagieren – beispielsweise INumber<TSelf> und IBinaryInteger<TSelf>. Die differenzierteren Schnittstellen wie IAdditionOperators<TSelf,TOther,TResult> und ITrigonometricFunctions<TSelf>unterstützen diese Typen und sind für Entwickler*innen verfügbar, die ihre eigenen domänenspezifischen numerischen Schnittstellen definieren.
- Numerische Schnittstellen
- Operatorschnittstellen
- Funktionsschnittstellen
- Analyse- und Formatierungsschnittstellen
Numerische Schnittstellen
Dieser Abschnitt enthält Informationen zu den Schnittstellen in System.Numerics, die zahlenähnliche Typen beschreiben, sowie zu den ihnen zur Verfügung stehenden Funktionen.
Schnittstellenname | Beschreibung |
---|---|
IBinaryFloatingPointIeee754<TSelf> | Macht APIs verfügbar, die üblicherweise für binäre Gleitkommatypen1 verwendet werden, die den IEEE 754-Standard implementieren. |
IBinaryInteger<TSelf> | Macht APIs verfügbar, die üblicherweise für binäre ganze Zahlen2 verwendet werden. |
IBinaryNumber<TSelf> | Macht APIs verfügbar, die üblicherweise für binäre Zahlen verwendet werden. |
IFloatingPoint<TSelf> | Macht APIs verfügbar, die üblicherweise für Gleitkommatypen verwendet werden. |
IFloatingPointIeee754<TSelf> | Macht APIs verfügbar, die üblicherweise für Gleitkommatypen verwendet werden, die den IEEE 754-Standard implementieren. |
INumber<TSelf> | Macht APIs verfügbar, die üblicherweise für vergleichbare Zahlentypen (faktisch die Domäne reeller Zahlen) verwendet werden. |
INumberBase<TSelf> | Macht APIs verfügbar, die üblicherweise für alle Zahlentypen (faktisch die Domäne komplexer Zahlen) verwendet werden. |
ISignedNumber<TSelf> | Macht APIs verfügbar, die üblicherweise für alle Zahlentypen mit Vorzeichen verwendet werden (beispielsweise für das Konzept NegativeOne ). |
IUnsignedNumber<TSelf> | Macht APIs verfügbar, die üblicherweise für alle Zahlentypen ohne Vorzeichen verwendet werden. |
IAdditiveIdentity<TSelf,TResult> | Macht das Konzept (x + T.AdditiveIdentity) == x verfügbar. |
IMinMaxValue<TSelf> | Macht die Konzepte T.MinValue und T.MaxValue verfügbar. |
IMultiplicativeIdentity<TSelf,TResult> | Macht das Konzept (x * T.MultiplicativeIdentity) == x verfügbar. |
1 Die binären Gleitkommatypen sind Double (double
), Half und Single (float
).
2 Die binären Ganzzahltypen sind Byte (byte
), Int16 (short
), Int32 (int
), Int64 (long
), Int128, IntPtr (nint
), SByte (sbyte
), UInt16 (ushort
), UInt32 (uint
), UInt64 (ulong
), UInt128 und UIntPtr (nuint
).
Die Schnittstelle, die Sie am wahrscheinlichsten direkt verwenden, ist INumber<TSelf>, was in etwa einer reellen Zahl entspricht. Wenn ein Typ diese Schnittstelle implementiert, bedeutet das, dass ein Wert über ein Vorzeichen verfügt (was unsigned
-Typen einschließt, da sie als positiv betrachtet werden) und dass er mit anderen Werten gleichen Typs verglichen werden kann. INumberBase<TSelf> bietet erweiterte Konzepte wie komplexe und imaginäre Zahlen – beispielsweise die Quadratwurzel einer negativen Zahl. Andere Schnittstellen (etwa IFloatingPointIeee754<TSelf>) wurden erstellt, da nicht alle Operationen für alle Zahlentypen sinnvoll sind. So ist beispielsweise die Berechnung der Untergrenze einer Zahl nur bei Gleitkommatypen sinnvoll. In der .NET-Basisklassenbibliothek wird IFloatingPointIeee754<TSelf> vom Gleitkommatyp Double implementiert, Int32 aber nicht.
Mehrere der Schnittstellen werden auch von verschiedenen anderen Typen implementiert, darunter Char, DateOnly, DateTime, DateTimeOffset, Decimal, Guid, TimeOnly und TimeSpan.
Die folgende Tabelle enthält einige der wichtigsten APIs, die von den einzelnen Schnittstellen verfügbar gemacht werden:
Schnittstelle | API-Name | Beschreibung |
---|---|---|
IBinaryInteger<TSelf> | DivRem |
Berechnet gleichzeitig den Quotienten und den Rest. |
LeadingZeroCount |
Ermittelt die Anzahl führender Nullbits in der Binärdarstellung. | |
PopCount |
Ermittelt die Anzahl festgelegter Bits in der Binärdarstellung. | |
RotateLeft |
Rotiert Bits nach links, was manchmal auch als kreisförmige Verschiebung nach links bezeichnet wird. | |
RotateRight |
Rotiert Bits nach rechts, was manchmal auch als kreisförmige Verschiebung nach rechts bezeichnet wird. | |
TrailingZeroCount |
Ermittelt die Anzahl nachfolgender Nullbits in der Binärdarstellung. | |
IFloatingPoint<TSelf> | Ceiling |
Rundet den Wert in Richtung positive Unendlichkeit. +4,5 wird zu +5, und -4,5 wird zu -4. |
Floor |
Rundet den Wert in Richtung negative Unendlichkeit. +4,5 wird zu +4, und -4,5 wird zu -5. | |
Round |
Rundet den Wert mit dem angegebenen Rundungsmodus. | |
Truncate |
Rundet den Wert in Richtung null. +4,5 wird zu +4, und -4,5 wird zu -4. | |
IFloatingPointIeee754<TSelf> | E |
Ruft einen Wert ab, der die eulersche Zahl für den Typ darstellt. |
Epsilon |
Ruft den kleinsten darstellbaren Wert ab, der für den Typ größer null ist. | |
NaN |
Ruft einen Wert ab, der NaN für den Typ darstellt. |
|
NegativeInfinity |
Ruft einen Wert ab, der -Infinity für den Typ darstellt. |
|
NegativeZero |
Ruft einen Wert ab, der -Zero für den Typ darstellt. |
|
Pi |
Ruft einen Wert ab, der Pi für den Typ darstellt. |
|
PositiveInfinity |
Ruft einen Wert ab, der +Infinity für den Typ darstellt. |
|
Tau |
Ruft einen Wert ab, der Tau (2 * Pi ) für den Typ darstellt. |
|
(Sonstige) | (Implementiert die vollständige Gruppe von Schnittstellen, die unter Funktionsschnittstellen aufgeführt sind.) | |
INumber<TSelf> | Clamp |
Beschränkt einen Wert so, dass er nicht größer als der angegebene Maximalwert und nicht kleiner als der angegebene Mindestwert ist. |
CopySign |
Legt das Vorzeichen eines angegebenen Werts auf das Vorzeichen eines anderen angegebenen Werts fest. | |
Max |
Gibt den größeren von zwei Werten zurück. Ist eine der beiden Eingaben NaN , wird NaN zurückgegeben. |
|
MaxNumber |
Gibt den größeren von zwei Werten zurück. Ist eine Eingabe NaN , wird die Zahl zurückgegeben. |
|
Min |
Gibt den kleineren von zwei Werten zurück. Ist eine der beiden Eingaben NaN , wird NaN zurückgegeben. |
|
MinNumber |
Gibt den kleineren von zwei Werten zurück. Ist eine Eingabe NaN , wird die Zahl zurückgegeben. |
|
Sign |
Gibt „-1“ für negative Werte, „0“ für null und „+1“ für positive Werte zurück. | |
INumberBase<TSelf> | One |
Ruft den Wert „1“ für den Typ ab. |
Radix |
Ruft die Basis für den Typ ab. Int32 gibt „2“ zurück. Decimal gibt „10“ zurück. | |
Zero |
Ruft den Wert „0“ für den Typ ab. | |
CreateChecked |
Erstellt einen Wert, und löst eine Überlaufausnahme (OverflowException) aus, wenn der Platz für die Eingabe nicht ausreicht.1 | |
CreateSaturating |
Erstellt einen Wert, und führt ein Clamping auf T.MinValue oder T.MaxValue durch, wenn der Platz für die Eingabe nicht ausreicht.1 |
|
CreateTruncating |
Erstellt einen Wert auf der Grundlage eines anderen Werts, und bricht ihn um, wenn der Platz für die Eingabe nicht ausreicht.1 | |
IsComplexNumber |
Gibt „true“ zurück, wenn der Wert einen reellen Teil ungleich null und einen imaginären Teil ungleich null hat. | |
IsEvenInteger |
Gibt „true“ zurück, wenn der Wert eine gerade ganze Zahl ist. Für „2,0“ wird true zurückgegeben. Für „2,2“ wird false zurückgegeben. |
|
IsFinite |
Gibt „true“ zurück, wenn der Wert nicht unendlich und nicht NaN ist. |
|
IsImaginaryNumber |
Gibt „true“ zurück, wenn der Wert einen reellen Teil hat, der null ist. Das bedeutet: 0 ist imaginär, 1 + 1i nicht. |
|
IsInfinity |
Gibt „true“ zurück, wenn der Wert Unendlichkeit darstellt. | |
IsInteger |
Gibt „true“ zurück, wenn der Wert eine ganze Zahl ist. Für „2,0“ und „3,0“ wird true zurückgegeben. Für „2,2“ und „3,1“ wird false zurückgegeben. |
|
IsNaN |
Gibt „true“ zurück, wenn der Wert NaN darstellt. |
|
IsNegative |
Gibt „true“ zurück, wenn der Wert negativ ist. Dies schließt „-0,0“ ein. | |
IsPositive |
Gibt „true“ zurück, wenn der Wert positiv ist. Dies schließt „0“ und „+0,0“ ein. | |
IsRealNumber |
Gibt „true“ zurück, wenn der Wert einen imaginären Teil hat, der null ist. Das bedeutet, dass „0“ sowie alle INumber<T> -Typen reell sind. |
|
IsZero |
Gibt „true“ zurück, wenn der Wert null darstellt. Dies schließt „0“, „+0,0“ und „-0,0“ ein. | |
MaxMagnitude |
Gibt den Wert mit einem höheren absoluten Wert zurück. Ist eine der Eingaben NaN , wird NaN zurückgegeben. |
|
MaxMagnitudeNumber |
Gibt den Wert mit einem höheren absoluten Wert zurück. Ist eine Eingabe NaN , wird die Zahl zurückgegeben. |
|
MinMagnitude |
Gibt den Wert mit einem niedrigeren absoluten Wert zurück. Ist eine der Eingaben NaN , wird NaN zurückgegeben. |
|
MinMagnitudeNumber |
Gibt den Wert mit einem niedrigeren absoluten Wert zurück. Ist eine Eingabe NaN , wird die Zahl zurückgegeben. |
|
ISignedNumber<TSelf> | NegativeOne |
Ruft den Wert „-1“ für den Typ ab. |
1 Sehen Sie sich zum besseren Verständnis des Verhaltens der drei Create*
-Methoden die folgenden Beispiele an:
Beispiel für einen zu großen Wert:
byte.CreateChecked(384)
löst eine Überlaufausnahme (OverflowException) aus.byte.CreateSaturating(384)
gibt „255“ zurück, da 384 größer als Byte.MaxValue (255) ist.byte.CreateTruncating(384)
gibt „128“ zurück, da die niedrigsten 8 Bits verwendet werden. (Die hexadezimale Darstellung von 384 ist0x0180
, und die niedrigsten 8 Bits sind0x80
, was 128 entspricht.)
Beispiel für einen zu kleinen Wert:
byte.CreateChecked(-384)
löst eine Überlaufausnahme (OverflowException) aus.byte.CreateSaturating(-384)
gibt „0“ zurück, da -384 kleiner als Byte.MinValue (0) ist.byte.CreateTruncating(-384)
gibt „128“ zurück, da die niedrigsten 8 Bits verwendet werden. (Die hexadezimale Darstellung von 384 ist0xFE80
, und die niedrigsten 8 Bits sind0x80
, was 128 entspricht.)
Bei den Create*
-Methoden müssen auch einige besondere Aspekte für IEEE 754-Gleitkommatypen wie float
und double
berücksichtigt werden, da sie über die speziellen Werte PositiveInfinity
, NegativeInfinity
und NaN
verfügen. Alle drei Create*
-APIs verhalten sich wie CreateSaturating
. MinValue
und MaxValue
stellen zwar die größte negative bzw. positive normale Zahl dar, die tatsächlichen Minimal- und Maximalwerte sind jedoch NegativeInfinity
und PositiveInfinity
, sodass stattdessen ein Clamping auf diese Werte durchgeführt wird.
Operatorschnittstellen
Die Operatorschnittstellen entsprechen den verschiedenen Operatoren, die für die Programmiersprache C# verfügbar sind.
- Operationen wie Multiplikation und Division werden explizit nicht gekoppelt, da dies nicht für alle Typen korrekt ist.
Matrix4x4 * Matrix4x4
ist beispielsweise gültig,Matrix4x4 / Matrix4x4
aber nicht. - Die Eingabe- und Ergebnistypen können sich üblicherweise unterscheiden, um Szenarien wie das Dividieren zweier ganzer Zahlen zu unterstützen, um einen
double
-Wert (beispielsweise3 / 2 = 1.5
) zu erhalten, oder um die Berechnung des Durchschnitts einer Reihe ganzer Zahlen zu ermöglichen.
Schnittstellenname | Definierte Operatoren |
---|---|
IAdditionOperators<TSelf,TOther,TResult> | x + y |
IBitwiseOperators<TSelf,TOther,TResult> | x & y , 'x | y', x ^ y und ~x |
IComparisonOperators<TSelf,TOther,TResult> | x < y , x > y , x <= y und x >= y |
IDecrementOperators<TSelf> | --x und x-- |
IDivisionOperators<TSelf,TOther,TResult> | x / y |
IEqualityOperators<TSelf,TOther,TResult> | x == y und x != y |
IIncrementOperators<TSelf> | ++x und x++ |
IModulusOperators<TSelf,TOther,TResult> | x % y |
IMultiplyOperators<TSelf,TOther,TResult> | x * y |
IShiftOperators<TSelf,TOther,TResult> | x << y und x >> y |
ISubtractionOperators<TSelf,TOther,TResult> | x - y |
IUnaryNegationOperators<TSelf,TResult> | -x |
IUnaryPlusOperators<TSelf,TResult> | +x |
Hinweis
Einige der Schnittstellen definieren neben einem regulären, nicht überprüften Operator auch einen überprüften Operator. Überprüfte Operatoren werden in überprüften Kontexten aufgerufen und ermöglichen es einem benutzerdefinierten Typ, das Überlaufverhalten zu definieren. Wenn Sie einen überprüften Operator (z. B. CheckedSubtraction(TSelf, TOther)) implementieren, müssen Sie auch den nicht überprüften Operator (z. B. Subtraction(TSelf, TOther)) implementieren.
Funktionsschnittstellen
Die Funktionsschnittstellen definieren allgemeine mathematische APIs, die weiter gefasst sind und nicht nur für eine spezifische numerische Schnittstelle gelten. Diese Schnittstellen werden alle von IFloatingPointIeee754<TSelf> implementiert. Möglicherweise werden sie in Zukunft auch von anderen relevanten Typen implementiert.
Schnittstellenname | Beschreibung |
---|---|
IExponentialFunctions<TSelf> | Macht Exponentialfunktionen verfügbar, die e^x , e^x - 1 , 2^x , 2^x - 1 , 10^x und 10^x - 1 unterstützen. |
IHyperbolicFunctions<TSelf> | Macht Hyperbelfunktionen verfügbar, die acosh(x) , asinh(x) , atanh(x) , cosh(x) , sinh(x) und tanh(x) unterstützen. |
ILogarithmicFunctions<TSelf> | Macht Logarithmusfunktionen verfügbar, die ln(x) , ln(x + 1) , log2(x) , log2(x + 1) , log10(x) und log10(x + 1) unterstützen. |
IPowerFunctions<TSelf> | Macht Potenzfunktionen verfügbar, die x^y unterstützen. |
IRootFunctions<TSelf> | Macht Wurzelfunktionen verfügbar, die cbrt(x) und sqrt(x) unterstützen. |
ITrigonometricFunctions<TSelf> | Macht trigonometrische Funktionen verfügbar, die acos(x) , asin(x) , atan(x) , cos(x) , sin(x) und tan(x) unterstützen. |
Analyse- und Formatierungsschnittstellen
Analyse und Formatierung sind zentrale Konzepte der Programmierung. Sie werden häufig beim Konvertieren von Benutzereingaben in einen bestimmten Typ oder beim Anzeigen eines Typs für Benutzer*innen verwendet. Diese Schnittstellen befinden sich im Namespace System.
Schnittstellenname | Beschreibung |
---|---|
IParsable<TSelf> | Macht die Unterstützung von T.Parse(string, IFormatProvider) und T.TryParse(string, IFormatProvider, out TSelf) verfügbar. |
ISpanParsable<TSelf> | Macht die Unterstützung von T.Parse(ReadOnlySpan<char>, IFormatProvider) und T.TryParse(ReadOnlySpan<char>, IFormatProvider, out TSelf) verfügbar. |
IFormattable1 | Macht die Unterstützung von value.ToString(string, IFormatProvider) verfügbar. |
ISpanFormattable1 | Macht die Unterstützung von value.TryFormat(Span<char>, out int, ReadOnlySpan<char>, IFormatProvider) verfügbar. |
1 Diese Schnittstelle ist weder neu noch generisch. Sie wird jedoch von allen Zahlentypen implementiert und stellt die Umkehroperation zu IParsable
dar.
Das folgende Programm verwendet beispielsweise zwei Zahlen als Eingabe und liest sie mithilfe einer generischen Methode, bei der der Typparameter auf IParsable<TSelf> beschränkt ist, aus der Konsole. Es berechnet den Durchschnitt mithilfe einer generischen Methode, bei der die Typparameter für die Eingabe- und Ergebniswerte auf INumber<TSelf> beschränkt sind, und zeigt das Ergebnis dann in der Konsole an.
using System.Globalization;
using System.Numerics;
static TResult Average<T, TResult>(T first, T second)
where T : INumber<T>
where TResult : INumber<TResult>
{
return TResult.CreateChecked( (first + second) / T.CreateChecked(2) );
}
static T ParseInvariant<T>(string s)
where T : IParsable<T>
{
return T.Parse(s, CultureInfo.InvariantCulture);
}
Console.Write("First number: ");
var left = ParseInvariant<float>(Console.ReadLine());
Console.Write("Second number: ");
var right = ParseInvariant<float>(Console.ReadLine());
Console.WriteLine($"Result: {Average<float, float>(left, right)}");
/* This code displays output similar to:
First number: 5.0
Second number: 6
Result: 5.5
*/
Siehe auch
- Blogbeitrag .NET 7 Preview 5 – Generic Math (.NET 7 Preview 5: Generische Mathematik)