Condividi tramite


Matematica generica

.NET 7 introduce nuove interfacce generiche correlate alla matematica nella libreria di classi di base. La disponibilità di queste interfacce significa che è possibile limitare un parametro di tipo di un tipo o di un metodo generico affinché sia simile a un numero. Inoltre, C# 11 e versioni successive consentono di definire static virtual i membri dell'interfaccia. Poiché gli operatori devono essere dichiarati come static, questa nuova funzionalità C# consente di dichiarare gli operatori nelle nuove interfacce per tipi simili a numeri.

Insieme, queste innovazioni consentono di eseguire operazioni matematiche in modo generico, ovvero senza dover conoscere il tipo esatto con cui si sta lavorando. Ad esempio, se si vuole scrivere un metodo che aggiunge due numeri, in precedenza era necessario aggiungere un overload del metodo per ogni tipo , ad esempio static int Add(int first, int second) e static float Add(float first, float second). È ora possibile scrivere un singolo metodo generico, in cui il parametro di tipo è vincolato a un tipo numerico. Per esempio:

static T Add<T>(T left, T right)
    where T : INumber<T>
{
    return left + right;
}

In questo metodo il parametro T di tipo è vincolato a un tipo che implementa la nuova INumber<TSelf> interfaccia. INumber<TSelf> implementa l'interfaccia IAdditionOperators<TSelf,TOther,TResult> , che contiene l'operatore + . Ciò consente al metodo di aggiungere in modo generico i due numeri. Il metodo può essere usato con uno qualsiasi dei tipi numerici predefiniti di .NET, perché sono stati tutti aggiornati per implementare INumber<TSelf> in .NET 7.

Gli autori di librerie trarranno maggior vantaggio dalle interfacce matematiche generiche, perché possono semplificare la codebase rimuovendo gli overload "ridondanti". Altri sviluppatori trarranno vantaggio indirettamente, perché le API usate possono iniziare a supportare più tipi.

Interfacce

Le interfacce sono state progettate per essere abbastanza dettagliate da permettere agli utenti di definire le proprie interfacce sopra, ma anche sufficientemente granulari da poter essere utilizzate facilmente. In tal modo, esistono alcune interfacce numeriche di base con cui la maggior parte degli utenti interagirà, ad esempio INumber<TSelf> e IBinaryInteger<TSelf>. Le interfacce con granularità più fine, ad esempio IAdditionOperators<TSelf,TOther,TResult> e ITrigonometricFunctions<TSelf>, supportano questi tipi e sono disponibili per gli sviluppatori che definiscono le proprie interfacce numeriche specifiche del dominio.

Interfacce numeriche

In questa sezione vengono descritte le interfacce in System.Numerics che descrivono tipi simili a numeri e le funzionalità a loro disponibili.

Nome interfaccia Descrizione
IBinaryFloatingPointIeee754<TSelf> Espone le API comuni ai tipi binari a virgola mobile1 che implementano lo standard IEEE 754.
IBinaryInteger<TSelf> Espone le API comuni ai numeri interi binari2.
IBinaryNumber<TSelf> Espone le API comuni ai numeri binari.
IFloatingPoint<TSelf> Espone le API comuni ai tipi a virgola mobile.
IFloatingPointIeee754<TSelf> Espone le API comuni ai tipi a virgola mobile che implementano lo standard IEEE 754.
INumber<TSelf> Espone le API comuni ai tipi di numeri confrontabili (in effetti il dominio numerico "reale").
INumberBase<TSelf> Espone le API comuni a tutti i tipi di numeri (in effetti il dominio numerico "complesso").
ISignedNumber<TSelf> Espone le API comuni a tutti i tipi di numeri firmati , ad esempio il concetto di NegativeOne.
IUnsignedNumber<TSelf> Espone le API comuni a tutti i tipi di numeri non firmati.
IAdditiveIdentity<TSelf,TResult> Espone il concetto di (x + T.AdditiveIdentity) == x.
IMinMaxValue<TSelf> Espone il concetto di T.MinValue e T.MaxValue.
IMultiplicativeIdentity<TSelf,TResult> Espone il concetto di (x * T.MultiplicativeIdentity) == x.

1I tipi binari a virgola mobile sono Double (double), Halfe Single (float).

2I tipi interi binari sono (Byte), byte (Int16), short (Int32), int (Int64), long, Int128 (IntPtr), nint (SByte), sbyte (UInt16), ushort (UInt32), uint (UInt64), ulong, e UInt128 (UIntPtr).

L'interfaccia che è più probabile usare direttamente è INumber<TSelf>, che corrisponde approssimativamente a un numero reale . Se un tipo implementa questa interfaccia, significa che un valore ha un segno (inclusi unsigned i tipi, che sono considerati positivi) e può essere confrontato con altri valori dello stesso tipo. INumberBase<TSelf> conferisce concetti più avanzati, ad esempio numeri complessi e immaginari , ad esempio la radice quadrata di un numero negativo. Sono state create altre interfacce, ad esempio IFloatingPointIeee754<TSelf>, perché non tutte le operazioni hanno senso per tutti i tipi di numero, ad esempio il calcolo del piano di un numero ha senso solo per i tipi a virgola mobile. Nella libreria di classi di base .NET il tipo Double a virgola mobile implementa IFloatingPointIeee754<TSelf> ma Int32 non lo fa.

Diverse interfacce vengono implementate anche da vari altri tipi, tra cui Char, , DateOnlyDateTime, DateTimeOffsetDecimalGuidTimeOnlye .TimeSpan

La tabella seguente illustra alcune delle API principali esposte da ogni interfaccia.

Interfaccia Nome API Descrizione
IBinaryInteger<TSelf> DivRem Calcola il quoziente e il resto contemporaneamente.
LeadingZeroCount Conta il numero di bit zero iniziali nella rappresentazione binaria.
PopCount Conta il numero di bit impostati nella rappresentazione binaria.
RotateLeft Ruota i bit a sinistra, talvolta chiamato anche rotazione circolare a sinistra.
RotateRight Ruota i bit a destra, talvolta chiamati anche spostamento circolare a destra.
TrailingZeroCount Conta il numero di bit finali zero nella rappresentazione binaria.
IFloatingPoint<TSelf> Ceiling Arrotonda il valore verso l'infinito positivo. +4.5 diventa +5 e -4.5 diventa -4.
Floor Arrotonda il valore verso l'infinito negativo. +4.5 diventa +4 e -4.5 diventa -5.
Round Arrotonda il valore utilizzando la modalità di arrotondamento specificata.
Truncate Arrotonda il valore verso zero. +4.5 diventa +4 e -4.5 diventa -4.
IFloatingPointIeee754<TSelf> E Ottiene un valore che rappresenta il numero di Eulero per il tipo.
Epsilon Ottiene il valore rappresentabile più piccolo maggiore di zero per il tipo.
NaN Ottiene un valore che rappresenta NaN per il tipo.
NegativeInfinity Ottiene un valore che rappresenta -Infinity per il tipo.
NegativeZero Ottiene un valore che rappresenta -Zero per il tipo.
Pi Ottiene un valore che rappresenta Pi per il tipo.
PositiveInfinity Ottiene un valore che rappresenta +Infinity per il tipo.
Tau Ottiene un valore che rappresenta Tau (2 * Pi) per il tipo.
(Altro) Implementa il set completo di interfacce elencate in Interfacce funzione.
INumber<TSelf> Clamp Limita un valore a non più e non inferiore al valore minimo e massimo specificato.
CopySign Imposta il segno di un valore specificato allo stesso segno di un altro valore specificato.
Max Restituisce il valore maggiore di due valori, restituendo NaN se uno degli input è NaN.
MaxNumber Restituisce il valore maggiore di due valori, restituendo il numero se un input è NaN.
Min Restituisce il valore minore di due valori, restituendo NaN se uno degli input è NaN.
MinNumber Restituisce il minore di due valori, restituendo il numero se un input è NaN.
Sign Restituisce -1 per i valori negativi, 0 per zero e +1 per i valori positivi.
INumberBase<TSelf> One Ottiene il valore 1 per il tipo.
Radix Ottiene la radice o la base del tipo. Int32 restituisce 2. Decimal restituisce 10.
Zero Ottiene il valore 0 per il tipo.
CreateChecked Crea un valore, generando un'eccezione OverflowException se l'input non può adattarsi.1
CreateSaturating Crea un valore, limitato a T.MinValue o T.MaxValue se l'input non può adattarsi.1
CreateTruncating Crea un valore da un altro valore, circondando se l'input non può adattarsi. 1
IsComplexNumber Restituisce vero se il valore ha una parte reale diversa da zero e una parte immaginaria diversa da zero.
IsEvenInteger Restituisce true se il valore è un numero intero pari. 2.0 restituisce truee 2.2 restituisce false.
IsFinite Restituisce true se il valore non è infinito e non NaN.
IsImaginaryNumber Restituisce vero se il valore ha una componente reale pari a zero. Questo significa che 0 è immaginario e 1 + 1i non lo è.
IsInfinity Restituisce true se il valore rappresenta l'infinito.
IsInteger Restituisce true se il valore è un numero intero. 2.0 e 3.0 restituiscono truee 2.2 e 3.1 restituiscono false.
IsNaN Restituisce true se il valore rappresenta NaN.
IsNegative Restituisce vero se il valore è negativo. Sono inclusi -0.0.
IsPositive Restituisce true se il valore è positivo. Sono inclusi 0 e +0,0.
IsRealNumber Restituisce vero se il valore ha una parte immaginaria pari a zero. Ciò significa che 0 è reale come tutti i INumber<T> tipi.
IsZero Restituisce true se il valore rappresenta zero. Sono inclusi 0, +0.0 e -0.0.
MaxMagnitude Restituisce il valore con un valore assoluto maggiore, restituendo NaN se uno degli input è NaN.
MaxMagnitudeNumber Restituisce il valore con un valore assoluto maggiore, restituendo il numero se un input è NaN.
MinMagnitude Restituisce il valore con un valore assoluto minore, restituendo NaN se uno degli input è NaN.
MinMagnitudeNumber Restituisce il valore con un valore assoluto minore, restituendo il numero se un input è NaN.
ISignedNumber<TSelf> NegativeOne Ottiene il valore -1 per il tipo.

1Per comprendere il comportamento dei tre Create* metodi, considerare gli esempi seguenti.

Esempio quando viene specificato un valore troppo grande:

  • byte.CreateChecked(384) genererà un'eccezione OverflowException.
  • byte.CreateSaturating(384) restituisce 255 perché 384 è maggiore di Byte.MaxValue (ovvero 255).
  • byte.CreateTruncating(384) restituisce 128 perché prende gli 8 bit meno significativi (384 ha una rappresentazione esadecimale di 0x0180, e gli 8 bit meno significativi sono 0x80, cioè 128).

Esempio quando viene specificato un valore troppo piccolo:

  • byte.CreateChecked(-384) genererà un'eccezione OverflowException.
  • byte.CreateSaturating(-384) restituisce 0 perché -384 è minore di Byte.MinValue (ovvero 0).
  • byte.CreateTruncating(-384) restituisce 128 perché prende gli 8 bit meno significativi (384 ha una rappresentazione esadecimale di 0xFE80, e gli 8 bit meno significativi sono 0x80, cioè 128).

I Create* metodi hanno anche alcune considerazioni speciali per i tipi a virgola mobile IEEE 754, come float e double, poiché hanno i valori speciali PositiveInfinity, NegativeInfinity e NaN. Tutte e tre Create* le API si comportano come CreateSaturating. Inoltre, mentre MinValue e MaxValue rappresentano il numero "normale" negativo/positivo più grande, i valori minimi e massimi effettivi sono NegativeInfinity e PositiveInfinity, quindi si bloccano a questi valori.

Interfacce operatore

Le interfacce dell'operatore corrispondono ai vari operatori disponibili per il linguaggio C#.

  • Non associano in modo esplicito operazioni come la moltiplicazione e la divisione perché non sono corrette per tutti i tipi. Ad esempio, Matrix4x4 * Matrix4x4 è valido, ma Matrix4x4 / Matrix4x4 non è valido.
  • In genere consentono ai tipi di input e di risultato di differire per supportare scenari quali la divisione di due interi per ottenere un doublevalore , ad esempio , 3 / 2 = 1.5o il calcolo della media di un set di interi.
Nome interfaccia Operatori definiti
IAdditionOperators<TSelf,TOther,TResult> x + y
IBitwiseOperators<TSelf,TOther,TResult> x & y, 'x | y', x ^ ye ~x
IComparisonOperators<TSelf,TOther,TResult> x < y, x > y, x <= ye x >= y
IDecrementOperators<TSelf> --x e x--
IDivisionOperators<TSelf,TOther,TResult> x / y
IEqualityOperators<TSelf,TOther,TResult> x == y e x != y
IIncrementOperators<TSelf> ++x e x++
IModulusOperators<TSelf,TOther,TResult> x % y
IMultiplyOperators<TSelf,TOther,TResult> x * y
IShiftOperators<TSelf,TOther,TResult> x << y e x >> y
ISubtractionOperators<TSelf,TOther,TResult> x - y
IUnaryNegationOperators<TSelf,TResult> -x
IUnaryPlusOperators<TSelf,TResult> +x

Annotazioni

Alcune interfacce definiscono un operatore controllato oltre a un normale operatore deselezionato. Gli operatori controllati vengono chiamati nei contesti controllati e consentono a un tipo definito dall'utente di definire il comportamento di overflow. Se si implementa un operatore verificato, ad esempio CheckedSubtraction(TSelf, TOther), è necessario implementare anche l'operatore non verificato, ad esempio Subtraction(TSelf, TOther).

Interfacce di funzione

Le interfacce di funzione definiscono API matematiche comuni che si applicano più ampiamente rispetto a un'interfaccia numerica specifica. Queste interfacce sono tutte implementate da IFloatingPointIeee754<TSelf>e possono essere implementate da altri tipi pertinenti in futuro.

Nome interfaccia Descrizione
IExponentialFunctions<TSelf> Espone funzioni esponenziali che supportano e^x, e^x - 1, 2^x2^x - 1, , 10^xe 10^x - 1.
IHyperbolicFunctions<TSelf> Espone funzioni iperboliche che supportano acosh(x), asinh(x), atanh(x), cosh(x), sinh(x)e tanh(x).
ILogarithmicFunctions<TSelf> Espone funzioni logaritmiche che supportano ln(x), ln(x + 1), log2(x), log2(x + 1)log10(x), e log10(x + 1).
IPowerFunctions<TSelf> Espone le funzioni di potenza che supportano x^y.
IRootFunctions<TSelf> Espone le funzioni radice che supportano cbrt(x) e sqrt(x).
ITrigonometricFunctions<TSelf> Espone le funzioni trigonometriche che supportano acos(x), asin(x), atan(x), cos(x), sin(x)e tan(x).

Analisi e formattazione delle interfacce

L'analisi e la formattazione sono concetti fondamentali nella programmazione. Vengono comunemente usati quando si converte l'input dell'utente in un determinato tipo o viene visualizzato un tipo all'utente. Queste interfacce si trovano nello spazio dei nomi System.

Nome interfaccia Descrizione
IParsable<TSelf> Espone il supporto per T.Parse(string, IFormatProvider) e T.TryParse(string, IFormatProvider, out TSelf).
ISpanParsable<TSelf> Espone il supporto per T.Parse(ReadOnlySpan<char>, IFormatProvider) e T.TryParse(ReadOnlySpan<char>, IFormatProvider, out TSelf).
IFormattable 1 Espone il supporto per value.ToString(string, IFormatProvider).
ISpanFormattable 1 Espone il supporto per value.TryFormat(Span<char>, out int, ReadOnlySpan<char>, IFormatProvider).

1Questa interfaccia non è nuova, né è generica. Tuttavia, viene implementato da tutti i tipi di numeri e rappresenta l'operazione inversa di IParsable.

Ad esempio, il programma seguente accetta due numeri come input, leggendoli dalla console usando un metodo generico in cui il parametro di tipo è vincolato a essere IParsable<TSelf>. Calcola la media usando un metodo generico in cui i parametri di tipo per i valori di input e risultato sono vincolati a essere INumber<TSelf>e quindi visualizzano il risultato nella 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
*/

Vedere anche