Mathématiques génériques
.NET 7 introduit de nouvelles interfaces génériques liées aux mathématiques dans la bibliothèque de classes de base. La disponibilité de ces interfaces signifie que vous pouvez contraindre un paramètre de type d’un type ou d’une méthode générique à être « semblable à un nombre ». En outre, C# 11 et versions ultérieures vous permet de définir des static virtual
membres d’interface. Étant donné que les opérateurs doivent être déclarés en tant que static
, cette nouvelle fonctionnalité C# permet aux opérateurs d’être déclarés dans les nouvelles interfaces pour les types semblables à des nombres.
Ensemble, ces innovations vous permettent d’effectuer des opérations mathématiques de manière générique, c’est-à-dire sans avoir besoin de connaître le type exact avec lequel vous travaillez. Par exemple, si vous souhaitiez écrire une méthode qui ajoute deux nombres, vous deviez précédemment ajouter une surcharge de la méthode pour chaque type (par exemple, static int Add(int first, int second)
et static float Add(float first, float second)
). Désormais, vous pouvez écrire une méthode générique unique, où le paramètre de type est contraint d’être un type numérique. Par exemple :
static T Add<T>(T left, T right)
where T : INumber<T>
{
return left + right;
}
Dans cette méthode, le paramètre de type T
est contraint d’être un type qui implémente la nouvelle interface INumber<TSelf>. INumber<TSelf> implémente l’interface IAdditionOperators<TSelf,TOther,TResult> , qui contient l’opérateur +. Cela permet à la méthode d’ajouter génériquement les deux nombres. La méthode peut être utilisée avec l’un des types numériques intégrés de NET, car ils ont tous été mis à jour pour implémenter INumber<TSelf> dans .NET 7.
Les auteurs de bibliothèque bénéficieront le plus des interfaces mathématiques génériques, car ils peuvent simplifier leur base de code en supprimant les surcharges « redondantes ». D’autres développeurs en bénéficieront indirectement, car les API qu’ils consomment peuvent commencer à prendre en charge davantage de types.
Les interfaces
Les interfaces ont été conçues pour être suffisamment affinées pour permettre aux utilisateurs de définir leurs propres interfaces en haut, tout en étant suffisamment granulaires pour être faciles à consommer. Dans cette mesure, il existe quelques interfaces numériques principales avec lesquelles la plupart des utilisateurs interagiront, par exemple INumber<TSelf> et IBinaryInteger<TSelf>. Les interfaces plus affinées, comme IAdditionOperators<TSelf,TOther,TResult> et ITrigonometricFunctions<TSelf>, prennent en charge ces types et sont disponibles pour les développeurs qui définissent leurs propres interfaces numériques spécifiques au domaine.
- Interfaces numériques
- Interfaces d’opérateur
- Interfaces de fonction
- Analyser et mettre en forme des interfaces
Interfaces numériques
Cette section décrit les interfaces dans System.Numerics qui décrivent les types similaires à des nombres et les fonctionnalités disponibles pour ceux-ci.
Nom de l’interface | Description |
---|---|
IBinaryFloatingPointIeee754<TSelf> | Expose les API communes aux types à virgule flottante binaire1 qui implémentent la norme IEEE 754. |
IBinaryInteger<TSelf> | Expose les API communes aux entiers binaires2. |
IBinaryNumber<TSelf> | Expose les API communes aux nombres binaires. |
IFloatingPoint<TSelf> | Expose les API communes aux types à virgule flottante. |
IFloatingPointIeee754<TSelf> | Expose les API communes aux types à virgule flottante qui implémentent la norme IEEE 754. |
INumber<TSelf> | Expose les API communes aux types de nombres comparables (effectivement le domaine des nombres « réels »). |
INumberBase<TSelf> | Expose les API communes à tous les types de nombres (effectivement le domaine des nombres « complexe »). |
ISignedNumber<TSelf> | Expose les API communes à tous les types de nombres signés (comme le concept de NegativeOne ). |
IUnsignedNumber<TSelf> | Expose les API communes à tous les types de nombres non signés. |
IAdditiveIdentity<TSelf,TResult> | Expose le concept de (x + T.AdditiveIdentity) == x . |
IMinMaxValue<TSelf> | Expose le concept de T.MinValue et T.MaxValue . |
IMultiplicativeIdentity<TSelf,TResult> | Expose le concept de (x * T.MultiplicativeIdentity) == x . |
1Les types à virgule flottante binaire sont Double (double
), Halfet Single (float
).
2Les types entiers binaires sont Byte (byte
), Int16 (short
), Int32 (int
), Int64 (long
), Int128, IntPtr (nint
), SByte (sbyte
), UInt16 (ushort
), UInt32 (uint
), UInt64 (ulong
), UInt128, et UIntPtr (nuint
).
L’interface que vous êtes le plus susceptible d’utiliser directement est INumber<TSelf>, ce qui correspond approximativement à un nombre réel. Si un type implémente cette interface, cela signifie qu’une valeur a un signe (ce qui inclut les types unsigned
, considérés comme positifs) et peut être comparée à d’autres valeurs du même type. INumberBase<TSelf> confère des concepts plus avancés, comme des nombres complexes et imaginaires, par exemple la racine carrée d’un nombre négatif. D’autres interfaces, comme IFloatingPointIeee754<TSelf>, ont été créées, car toutes les opérations n’ont pas de sens pour tous les types de nombres, par exemple, le calcul de la valeur plancher d’un nombre n’a de sens que pour les types à virgule flottante. Dans la bibliothèque de classes de base .NET, le type à virgule flottante Double implémente IFloatingPointIeee754<TSelf>, mais Int32 ne l’implémente pas.
Plusieurs interfaces sont également implémentées par différents autres types, notamment Char, DateOnly, DateTime, DateTimeOffset, Decimal, Guid, TimeOnly et TimeSpan.
Le tableau suivant présente quelques-unes des API principales exposées par chaque interface.
Interface | Nom de l’API | Description |
---|---|---|
IBinaryInteger<TSelf> | DivRem |
Calcule simultanément le quotient et le reste. |
LeadingZeroCount |
Compte le nombre de bits zéros non significatifs dans la représentation binaire. | |
PopCount |
Compte le nombre de bits définis dans la représentation binaire. | |
RotateLeft |
Fait pivoter les bits vers la gauche, parfois également appelé décalage circulaire vers la gauche. | |
RotateRight |
Fait pivoter les bits vers la droite, parfois également appelé décalage circulaire vers la droite. | |
TrailingZeroCount |
Compte le nombre de bits zéros de fin dans la représentation binaire. | |
IFloatingPoint<TSelf> | Ceiling |
Arrondit la valeur vers l’infini positif. +4,5 devient +5, et -4,5 devient -4. |
Floor |
Arrondit la valeur vers l’infini négatif. +4,5 devient +4, et -4,5 devient -5. | |
Round |
Arrondit la valeur en utilisant le mode d’arrondi spécifié. | |
Truncate |
Arrondit la valeur vers zéro. +4,5 devient +4, et -4,5 devient -4. | |
IFloatingPointIeee754<TSelf> | E |
Obtient une valeur représentant le nombre d’Euler pour le type. |
Epsilon |
Obtient la valeur la plus petite pouvant être représentée supérieure à zéro pour le type. | |
NaN |
Obtient une valeur représentant NaN pour le type. |
|
NegativeInfinity |
Obtient une valeur représentant -Infinity pour le type. |
|
NegativeZero |
Obtient une valeur représentant -Zero pour le type. |
|
Pi |
Obtient une valeur représentant Pi pour le type. |
|
PositiveInfinity |
Obtient une valeur représentant +Infinity pour le type. |
|
Tau |
Obtient une valeur représentant Tau (2 * Pi ) pour le type. |
|
(Autre) | (Implémente l’ensemble complet d’interfaces répertoriées sous Interfaces de fonction.) | |
INumber<TSelf> | Clamp |
Limite une valeur à pas plus et pas moins que la valeur minimale et maximale spécifiée. |
CopySign |
Définit le signe d’une valeur spécifiée sur la même valeur qu’une autre valeur spécifiée. | |
Max |
Renvoie la valeur la plus élevée de deux valeurs, en renvoyant NaN si l’une des entrées est NaN . |
|
MaxNumber |
Renvoie la valeur la plus basse de deux valeurs, en renvoyant le nombre si une entrée est NaN . |
|
Min |
Renvoie la valeur la plus basse de deux valeurs, en renvoyant NaN si l’une des entrées est NaN . |
|
MinNumber |
Renvoie la valeur la plus basse de deux valeurs, en renvoyant le nombre si une entrée est NaN . |
|
Sign |
Renvoie -1 pour les valeurs négatives, 0 pour zéro et +1 pour les valeurs positives. | |
INumberBase<TSelf> | One |
Obtient la valeur 1 pour le type. |
Radix |
Obtient la base pour le type. Int32 renvoie 2. Decimal renvoie 10. | |
Zero |
Obtient la valeur 0 pour le type. | |
CreateChecked |
Crée une valeur, en levant une OverflowException si l’entrée ne peut pas être ajustée.1 | |
CreateSaturating |
Crée une valeur, en fixant à T.MinValue ou T.MaxValue si l’entrée ne peut pas être ajustée.1 |
|
CreateTruncating |
Crée une valeur à partir d’une autre valeur, en enveloppant si l’entrée ne peut pas être ajustée.1 | |
IsComplexNumber |
Renvoie true si la valeur a une partie réelle non nulle et une partie imaginaire non nulle. | |
IsEvenInteger |
Renvoie true si la valeur est un entier pair. 2,0 renvoie true et 2,2 renvoie false . |
|
IsFinite |
Renvoie true si la valeur n’est pas infinie et n’est pas NaN . |
|
IsImaginaryNumber |
Renvoie true si la valeur a une partie réelle nulle. Cela signifie que 0 est imaginaire et 1 + 1i ne l’est pas. |
|
IsInfinity |
Renvoie true si la valeur représente l’infini. | |
IsInteger |
Renvoie true si la valeur est un entier. 2,0 et 3,0 renvoient true et 2,2 et 3,1 renvoient false . |
|
IsNaN |
Renvoie true si la valeur représente NaN . |
|
IsNegative |
Renvoie true si la valeur est négative. Cela inclut -0,0. | |
IsPositive |
Renvoie true si la valeur est positive. Cela inclut 0 et +0,0. | |
IsRealNumber |
Renvoie true si la valeur a une partie imaginaire nulle. Cela signifie que 0 est réel, de même que tous les types INumber<T> . |
|
IsZero |
Renvoie true si la valeur représente zéro. Cela inclut 0, +0,0 et -0,0. | |
MaxMagnitude |
Renvoie la valeur avec la valeur absolue la plus élevée, en renvoyant NaN si l’une des entrées est NaN . |
|
MaxMagnitudeNumber |
Renvoie la valeur avec la valeur absolue la plus élevée, en renvoyant le nombre si une entrée est NaN . |
|
MinMagnitude |
Renvoie la valeur avec la valeur absolue la plus basse, en renvoyant NaN si l’une des entrées est NaN . |
|
MinMagnitudeNumber |
Renvoie la valeur avec la valeur absolue la plus basse, en renvoyant le nombre si une entrée est NaN . |
|
ISignedNumber<TSelf> | NegativeOne |
Obtient la valeur -1 pour le type. |
1Pour mieux comprendre le comportement des trois méthodes Create*
, examinez les exemples suivants.
Exemple lorsqu’une valeur est trop élevée :
byte.CreateChecked(384)
lève un OverflowException.byte.CreateSaturating(384)
renvoie 255, car 384 est supérieur à Byte.MaxValue (qui est 255).byte.CreateTruncating(384)
renvoie 128, car il prend les 8 bits les plus bas (384 a une représentation hexadécimale de0x0180
, et les 8 bits les plus bas sont0x80
, qui est 128).
Exemple lorsqu’une valeur est trop petite :
byte.CreateChecked(-384)
lève un OverflowException.byte.CreateSaturating(-384)
renvoie 0, car -384 est inférieure à Byte.MinValue (qui est 0).byte.CreateTruncating(-384)
renvoie 128, car il prend les 8 bits les plus bas (384 a une représentation hexadécimale de0xFE80
, et les 8 bits les plus bas sont0x80
, qui est 128).
Les méthodes Create*
ont également des éléments spécifiques à prendre en considération pour les types à virgule flottante IEEE 754, comme float
et double
, car ceux-ci ont les valeurs spéciales PositiveInfinity
, NegativeInfinity
et NaN
. Les trois API Create*
se comportent comme CreateSaturating
. En outre, tandis que MinValue
et MaxValue
représentent le plus grand nombre négatif/positif « normal », les valeurs minimales et maximales réelles sont NegativeInfinity
et PositiveInfinity
, par conséquent, elles sont limitées à ces valeurs à la place.
Interfaces d’opérateur
Les interfaces d’opérateur correspondent aux différents opérateurs disponibles pour le langage C#.
- Ils n’associent explicitement pas les opérations comme la multiplication et la division, car cela n’est pas correct pour tous les types. Par exemple,
Matrix4x4 * Matrix4x4
est valide, maisMatrix4x4 / Matrix4x4
n’est pas valide. - Ils permettent généralement aux types d’entrée et de résultat de différer pour prendre en charge des scénarios comme la division de deux entiers pour obtenir un
double
, par exemple,3 / 2 = 1.5
ou le calcul de la moyenne d’un ensemble d’entiers.
Nom de l’interface | Opérateurs définis |
---|---|
IAdditionOperators<TSelf,TOther,TResult> | x + y |
IBitwiseOperators<TSelf,TOther,TResult> | x & y , 'x | y', x ^ y et ~x |
IComparisonOperators<TSelf,TOther,TResult> | x < y , x > y , x <= y et x >= y |
IDecrementOperators<TSelf> | --x et x-- |
IDivisionOperators<TSelf,TOther,TResult> | x / y |
IEqualityOperators<TSelf,TOther,TResult> | x == y et x != y |
IIncrementOperators<TSelf> | ++x et x++ |
IModulusOperators<TSelf,TOther,TResult> | x % y |
IMultiplyOperators<TSelf,TOther,TResult> | x * y |
IShiftOperators<TSelf,TOther,TResult> | x << y et x >> y |
ISubtractionOperators<TSelf,TOther,TResult> | x - y |
IUnaryNegationOperators<TSelf,TResult> | -x |
IUnaryPlusOperators<TSelf,TResult> | +x |
Notes
Certaines interfaces définissent un opérateur vérifié en plus d’un opérateur non vérifié normal. Les opérateurs vérifiés sont appelés dans les contextes vérifiés et autorisent un type défini par l’utilisateur à définir le comportement en cas de dépassement. Si vous implémentez un opérateur vérifié, par exemple, CheckedSubtraction(TSelf, TOther), vous devez également implémenter l’opérateur non vérifié, par exemple Subtraction(TSelf, TOther).
Interfaces de fonction
Les interfaces de fonction définissent des API mathématiques courantes qui s’appliquent plus largement qu’à une interface numérique spécifique. Ces interfaces sont toutes implémentées par IFloatingPointIeee754<TSelf>, et peuvent être implémentées par d’autres types pertinents à l’avenir.
Nom de l’interface | Description |
---|---|
IExponentialFunctions<TSelf> | Expose les fonctions exponentielles prenant en charge e^x , e^x - 1 , 2^x , 2^x - 1 , 10^x et 10^x - 1 . |
IHyperbolicFunctions<TSelf> | Expose les fonctions hyperboliques prenant en charge acosh(x) , asinh(x) , atanh(x) , cosh(x) , sinh(x) et tanh(x) . |
ILogarithmicFunctions<TSelf> | Expose les fonctions logarithmiques prenant en charge ln(x) , ln(x + 1) , log2(x) , log2(x + 1) , log10(x) et log10(x + 1) . |
IPowerFunctions<TSelf> | Expose les fonctions de puissances prenant en charge x^y . |
IRootFunctions<TSelf> | Expose les fonctions racines prenant en charge cbrt(x) et sqrt(x) . |
ITrigonometricFunctions<TSelf> | Expose les fonctions trigonométriques prenant en charge acos(x) , asin(x) , atan(x) , cos(x) , sin(x) et tan(x) . |
Analyser et mettre en forme des interfaces
L’analyse et la mise en forme sont des concepts fondamentaux en programmation. Ils sont couramment utilisés lors de la conversion de l’entrée utilisateur en un type donné ou lors de l’affichage d’un type pour l’utilisateur. Ces interfaces se trouvent dans l’espace de noms System.
Nom de l’interface | Description |
---|---|
IParsable<TSelf> | Expose la prise en charge de T.Parse(string, IFormatProvider) et T.TryParse(string, IFormatProvider, out TSelf) . |
ISpanParsable<TSelf> | Expose la prise en charge de T.Parse(ReadOnlySpan<char>, IFormatProvider) et T.TryParse(ReadOnlySpan<char>, IFormatProvider, out TSelf) . |
IFormattable1 | Expose la prise en charge de value.ToString(string, IFormatProvider) . |
ISpanFormattable1 | Expose la prise en charge de value.TryFormat(Span<char>, out int, ReadOnlySpan<char>, IFormatProvider) . |
1Cette interface n’est pas nouvelle, ni générique. Toutefois, elle est implémentée par tous les types de nombres et représente l’opération inverse de IParsable
.
Par exemple, le programme suivant prend deux nombres comme entrée, les lisant à partir de la console à l’aide d’une méthode générique où le paramètre de type est contraint d’être IParsable<TSelf>. Il calcule la moyenne à l’aide d’une méthode générique où les paramètres de type pour les valeurs d’entrée et de résultat sont contraintes d’être INumber<TSelf>, puis affiche le résultat dans la console.
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
*/