Teilen über


Generische Mathematik

.NET 7 führt neue mathematische generische Schnittstellen in die Basisklassenbibliothek ein. Die Verfügbarkeit dieser Schnittstellen bedeutet, dass Sie einen Typparameter eines generischen Typs oder einer methode auf "zahlähnliche" beschränken können. Darüber hinaus können Sie mit C# 11 und höher static virtual definieren. Da Operatoren als static deklariert werden müssen, ermöglicht es dieses neue C#-Feature, dass Operatoren in den neuen Schnittstellen für nummernähnliche Typen deklariert werden können.

Diese Innovationen ermöglichen es Ihnen, mathematische Vorgänge generisch durchzuführen, d. h., ohne den genauen Typ kennen zu müssen, mit dem Sie arbeiten. Wenn Sie beispielsweise eine Methode schreiben möchten, die zwei Zahlen hinzufügt, mussten Sie zuvor eine Überladung der Methode für jeden Typ hinzufügen (z. B. 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;
}

Bei dieser Methode ist der Typparameter T auf einen Typ beschränkt, der die neue INumber<TSelf> Schnittstelle implementiert. INumber<TSelf> implementiert die IAdditionOperators<TSelf,TOther,TResult> Schnittstelle, die den Operator +enthält. Dies ermöglicht es der Methode, die beiden Zahlen generisch hinzuzufügen. Die Methode kann mit allen eingebauten numerischen Typen von .NET verwendet werden, da sie alle aktualisiert wurden, um INumber<TSelf> in .NET 7 zu implementieren.

Bibliotheksautoren profitieren am meisten von den generischen mathematischen Schnittstellen, da sie ihre Codebasis vereinfachen können, indem sie "redundante" Überladungen entfernen. Andere Entwickler profitieren indirekt, da die von ihnen genutzten APIs mit der Unterstützung weiterer Typen beginnen können.

Die Schnittstellen

Die Schnittstellen wurden so konzipiert, dass sie so fein abgestimmt sind, dass Benutzer ihre eigenen Schnittstellen oben definieren können, während sie auch präzise genug sind, um sie einfach zu nutzen. In diesem Zusammenhang gibt es einige wichtige numerische Schnittstellen, mit denen die meisten Benutzer interagieren, wie INumber<TSelf> und IBinaryInteger<TSelf>. Die feineren Schnittstellen, wie IAdditionOperators<TSelf,TOther,TResult> und ITrigonometricFunctions<TSelf>, unterstützen diese Typen und sind für Entwickler verfügbar, die ihre eigenen domänenspezifischen numerischen Schnittstellen definieren.

Numerische Schnittstellen

In diesem Abschnitt werden die Schnittstellen in System.Numerics beschrieben, die nummernähnliche Typen und die für sie verfügbaren Funktionen umfassen.

Schnittstellenname BESCHREIBUNG
IBinaryFloatingPointIeee754<TSelf> Stellt APIs bereit, die für binäre Gleitkommatypen1 gemeinsam sind und den IEEE 754-Standard implementieren.
IBinaryInteger<TSelf> Macht APIs verfügbar, die für binäre ganze Zahlen2 gemeinsam sind.
IBinaryNumber<TSelf> Macht APIs verfügbar, die für Binäre Zahlen üblich sind.
IFloatingPoint<TSelf> Macht APIs verfügbar, die für Gleitkommatypen gemeinsam sind.
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 für vergleichbare Zahlentypen (effektiv die Domäne der realen Zahl) gemeinsam sind.
INumberBase<TSelf> Macht APIs verfügbar, die allen Nummerntypen gemeinsam sind (effektiv die "komplexe" Nummerndomäne).
ISignedNumber<TSelf> Macht APIs verfügbar, die allen signierten Nummerntypen gemeinsam sind (z. B. das Konzept von NegativeOne).
IUnsignedNumber<TSelf> Macht APIs verfügbar, die allen nicht signierten Nummerntypen gemeinsam sind.
IAdditiveIdentity<TSelf,TResult> Legt das Konzept von (x + T.AdditiveIdentity) == x offen.
IMinMaxValue<TSelf> Legt das Konzept von T.MinValue und T.MaxValue offen.
IMultiplicativeIdentity<TSelf,TResult> Legt das Konzept von (x * T.MultiplicativeIdentity) == x offen.

1Die binären Gleitkommatypen sind Double (double), Halfund Single (float).

2 Die binären ganzzahligen Typen 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 ehesten direkt verwenden, ist INumber<TSelf>, was ungefähr einer reellen Zahl entspricht. Wenn ein Typ diese Schnittstelle implementiert, bedeutet dies, dass ein Wert ein Vorzeichen hat (einschließlich unsigned-Typen, die als positiv betrachtet werden) und mit anderen Werten desselben Typs verglichen werden kann. INumberBase<TSelf> Es werden komplexere Konzepte wie komplexe und imaginäre Zahlen, z. B. die Quadratwurzel einer negativen Zahl, vermittelt. Weitere Schnittstellen, z. B. IFloatingPointIeee754<TSelf>, wurden erstellt, da nicht alle Vorgänge für alle Zahlentypen sinnvoll sind—z. B. ist die Berechnung des Bodenwerts einer Zahl nur für Gleitkommatypen sinnvoll. In der .NET-Basisklassenbibliothek wird Double vom Gleitkommatyp IFloatingPointIeee754<TSelf> implementiert, Int32 aber nicht.

Mehrere der Schnittstellen werden auch von verschiedenen anderen Typen implementiert, darunter Char, , DateOnly, DateTime, DateTimeOffset, Decimal, Guid, und TimeOnlyTimeSpan.

In der folgenden Tabelle sind einige der kernigen APIs aufgeführt, die von jeder Schnittstelle verfügbar gemacht werden.

Schnittstelle API-Name BESCHREIBUNG
IBinaryInteger<TSelf> DivRem Berechnet den Quotienten und Rest gleichzeitig.
LeadingZeroCount Zählt die Anzahl der führenden Nullbits in der binären Darstellung.
PopCount Zählt die Anzahl der festgelegten Bits in der binären Darstellung.
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 Zählt die Anzahl der nachfolgenden Nullbits in der binären Darstellung.
IFloatingPoint<TSelf> Ceiling Rundet den Wert in Richtung positive Unendlichkeit. +4,5 wird +5, und -4,5 wird -4.
Floor Rundet den Wert in Richtung negative Unendlichkeit. +4,5 wird +4, und -4,5 wird -5.
Round Rundet den Wert mithilfe des angegebenen Rundungsmodus ab.
Truncate Rundet den Wert auf Null. +4,5 wird +4, und -4,5 wird -4.
IFloatingPointIeee754<TSelf> E Ruft einen Wert ab, der die Zahl von Euler für den Typ darstellt.
Epsilon Ruft den kleinsten darstellbaren Wert ab, der größer als Null für den Typ 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 den vollständigen Satz von Schnittstellen, die unter Funktionsschnittstellen aufgeführt sind.)
INumber<TSelf> Clamp Schränkt einen Wert auf nicht mehr und nicht weniger als den angegebenen Min- und Maximalwert ein.
CopySign Legt das Vorzeichen eines angegebenen Werts so fest, dass es dasselbe ist wie das eines anderen angegebenen Werts.
Max Gibt den größeren von zwei Werten zurück, wobei NaN zurückgegeben wird, wenn eine der Eingaben NaN ist.
MaxNumber Gibt den größeren von zwei Werten zurück, wenn eine Eingabe NaN ist, wird die Zahl zurückgegeben.
Min Gibt den kleineren von zwei Werten zurück, wobei NaN zurückgegeben wird, wenn eine der beiden Eingaben NaN ist.
MinNumber Gibt den kleineren von zwei Werten zurück, wobei die Zahl zurückgegeben wird, wenn eine Eingabe ist NaN.
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, der auf T.MinValue oder T.MaxValue begrenzt wird, wenn die Eingabe nicht passen kann.1
CreateTruncating Erstellt einen Wert aus einem anderen Wert, der wiederholt wird, wenn die Eingabe nicht passt. 1
IsComplexNumber Gibt "true" zurück, wenn der Wert einen realen Teil ungleich Null und einen Nicht-Null-Imaginärteil aufweist.
IsEvenInteger Gibt true zurück, wenn der Wert eine gerade ganze Zahl ist. 2.0 gibt zurück true, und 2,2 gibt zurück false.
IsFinite Gibt true zurück, wenn der Wert nicht unendlich ist und nicht NaN.
IsImaginaryNumber Gibt wahr zurück, wenn der Wert einen Realteil von null hat. Das bedeutet, dass 0 imaginär ist und 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. 2.0 und 3.0 geben true zurück, und 2.2 und 3.1 geben false zurück.
IsNaN Gibt true zurück, wenn der Wert NaN repräsentiert.
IsNegative Gibt true zurück, wenn der Wert negativ ist. Dies umfasst -0.0.
IsPositive Gibt true zurück, wenn der Wert positiv ist. Dies umfasst 0 und +0,0.
IsRealNumber Gibt true zurück, wenn der Wert einen null imaginären Teil aufweist. Dies bedeutet, dass 0 real ist, wie alle INumber<T> Arten.
IsZero Gibt true zurück, wenn der Wert Null darstellt. Dies umfasst 0, +0,0 und -0,0.
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 größeren absoluten Wert zurück, wobei die Zahl zurückgegeben wird, wenn eine Eingabe ist NaN.
MinMagnitude Gibt den Wert mit einem niedrigeren absoluten Wert zurück und gibt NaN zurück, falls eine der Eingaben NaN ist.
MinMagnitudeNumber Gibt den Wert mit einem niedrigeren absoluten Wert zurück, wobei die Zahl zurückgegeben wird, wenn eine Eingabe ist NaN.
ISignedNumber<TSelf> NegativeOne Ruft den Wert „-1“ für den Typ ab.

1Beachten Sie die folgenden Beispiele, um das Verhalten der drei Create* Methoden zu verstehen.

Beispiel für einen Wert, der zu groß ist:

  • byte.CreateChecked(384) löst eine 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 es die niedrigsten 8 Bits dauert (384 hat eine Hexadendarstellung von 0x0180, und die niedrigsten 8 Bits ist 0x80, was 128 ist).

Beispiel für einen Wert, der zu klein ist:

  • byte.CreateChecked(-384) löst eine 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 es die niedrigsten 8 Bits dauert (384 hat eine Hexadendarstellung von 0xFE80, und die niedrigsten 8 Bits ist 0x80, was 128 ist).

Die Create* Methoden weisen auch einige besondere Aspekte für IEEE 754-Gleitkommatypen auf, wie float und double, da sie die speziellen Werte PositiveInfinity, NegativeInfinityund NaN. Alle drei Create* APIs verhalten sich wie CreateSaturating. Außerdem stellen MinValue und MaxValue die größte negative/positive "normal"-Zahl dar, während die tatsächlichen Mindest- und Höchstwerte NegativeInfinity und PositiveInfinity sind, sodass sie stattdessen auf diese Werte begrenzt werden.

Operatorschnittstellen

Die Operatorschnittstellen entsprechen den verschiedenen Operatoren, die für die C#-Sprache verfügbar sind.

  • Sie koppeln explizit keine Vorgänge wie Multiplikation und Division, da dies für alle Typen nicht korrekt ist. Beispielsweise Matrix4x4 * Matrix4x4 ist gültig, aber Matrix4x4 / Matrix4x4 nicht gültig.
  • Sie ermöglichen es in der Regel, dass Eingabewerte und Ergebnistypen unterschiedlich sein können, um Szenarien zu unterstützen, wie z. B. das Teilen zweier ganzer Zahlen, um einen double zu erhalten, oder die Berechnung des Mittelwerts einer Reihe ganzer Zahlen.
Schnittstellenname Definierte Operatoren
IAdditionOperators<TSelf,TOther,TResult> x + y
IBitwiseOperators<TSelf,TOther,TResult> x & y, 'x | y', x ^ yund ~x
IComparisonOperators<TSelf,TOther,TResult> x < y, x > y, x <= yund 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 breiter als auf eine bestimmte numerische Schnittstelle angewendet werden. Diese Schnittstellen werden alle von IFloatingPointIeee754<TSelf> implementiert und können in Zukunft von anderen relevanten Typen implementiert werden.

Schnittstellenname BESCHREIBUNG
IExponentialFunctions<TSelf> Macht exponentielle Funktionen verfügbar, die e^x, e^x - 1, 2^x, 2^x - 1, 10^x und 10^x - 1 unterstützen.
IHyperbolicFunctions<TSelf> Macht hyperbolische Funktionen sichtbar, die acosh(x), asinh(x), atanh(x), cosh(x), sinh(x) und tanh(x) unterstützen.
ILogarithmicFunctions<TSelf> Exponiert logarithmische Funktionen, 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 Stammfunktionen 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.

Analysieren und Formatieren von Schnittstellen

Die Analyse und Formatierung sind Kernkonzepte bei der Programmierung. Sie werden häufig beim Konvertieren von Benutzereingaben in einen bestimmten Typ oder beim Anzeigen eines Typs für den Benutzer verwendet. Diese Schnittstellen befinden sich im System Namespace.

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.
IFormattable 1 Macht die Unterstützung von value.ToString(string, IFormatProvider) verfügbar.
ISpanFormattable 1 Macht die Unterstützung von value.TryFormat(Span<char>, out int, ReadOnlySpan<char>, IFormatProvider) verfügbar.

1Diese Schnittstelle ist weder neu noch generisch. Es wird jedoch von allen Zahlentypen implementiert und stellt die umgekehrte Operation von 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. Er berechnet den Mittelwert mithilfe einer generischen Methode, bei der die Typparameter für die Eingabe- und Ergebniswerte eingeschränkt sind INumber<TSelf>, und zeigt dann das Ergebnis 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