Operazioni matematiche generiche
.NET 7 introduce nuove interfacce generiche relative alla matematica nella libreria di classi base. Grazie alla disponibilità di queste interfacce, è possibile vincolare un parametro di tipo di un tipo o metodo generico perché sia numerico. Inoltre, C# 11 e versioni successive consentono di definire i static virtual
membri dell’interfaccia. Poiché gli operatori devono essere dichiarati come static
, questa nuova funzionalità C# consente di dichiararli nelle nuove interfacce per tipi numerici.
Insieme, queste innovazioni consentono di eseguire operazioni matematiche in modo generico, ovvero senza dover conoscere il tipo esatto in uso. Se, ad esempio, si desiderava scrivere un metodo che somma due numeri, era in precedenza necessario aggiungere un overload del metodo per ogni tipo (per 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 simile a un numero. Ad esempio:
static T Add<T>(T left, T right)
where T : INumber<T>
{
return left + right;
}
In questo metodo, il parametro di tipo T
è vincolato a un tipo che implementa la nuova interfaccia INumber<TSelf>. INumber<TSelf> implementa l'interfaccia IAdditionOperators<TSelf,TOther,TResult>, che contiene l’operatore +. Ciò consente al metodo di sommare i due numeri in modo generico. Il metodo può essere usato con qualsiasi tipo numerico integrato di .NET, poiché sono tutti aggiornati per implementare INumber<TSelf> in .NET 7.
Le interfacce matematiche generiche saranno utili soprattutto agli autori di librerie, poiché permettono loro di semplificare il loro codice di base rimuovendo eventuali overload "ridondanti". Altri sviluppatori ne trarranno vantaggio indirettamente, poiché le API da loro utilizzate potranno iniziare a supportare più tipi.
Interfacce
Le interfacce sono state progettate per essere sufficientemente dettagliate da permettere agli utenti di definire le proprie interfacce in primo piano, ma anche abbastanza granulari da essere usate facilmente. In tal senso, esistono alcune interfacce numeriche di base con cui la maggior parte degli utenti interagirà (ad esempio INumber<TSelf> e IBinaryInteger<TSelf>). Le interfacce più dettagliate, come IAdditionOperators<TSelf,TOther,TResult> e ITrigonometricFunctions<TSelf>, supportano questi tipi e sono disponibili per sviluppatori che definiscono le proprie interfacce numeriche specifiche del dominio.
- Interfacce numeriche
- Interfacce operatore
- Interfacce di funzione
- Analisi e formattazione delle interfacce
Interfacce numeriche
In questa sezione sono descritte le interfacce in System.Numerics che descrivono tipi numerici e le funzionalità in esse disponibili.
Nome interfaccia | Descrizione |
---|---|
IBinaryFloatingPointIeee754<TSelf> | Espone le API comuni ai tipi binari a virgola mobile 1 che implementano lo standard IEEE 754. |
IBinaryInteger<TSelf> | Espone le API comuni ai numeri interi binari 2. |
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 (essenzialmente, il dominio numerico "reale"). |
INumberBase<TSelf> | Espone le API comuni a tutti i tipi di numeri (essenzialmente, 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 i concetti 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
), Half e Single (float
).
2I tipi binari con numeri interi sono Byte (byte
), Int16 (short
), Int32 (int
), Int64 (long
), Int128, IntPtr (nint
), SByte (sbyte
), UInt16 (ushort
), UInt32 (uint
), UInt64 (ulong
), UInt128 e UIntPtr (nuint
).
L'interfaccia che è più probabile usare direttamente è INumber<TSelf>, la quale corrisponde approssimativamente a un numero reale. Se un tipo implementa questa interfaccia, significa che un valore include un segno (inclusi i tipi unsigned
, considerati positivi) e può essere confrontato con altri valori dello stesso tipo. INumberBase<TSelf> conferisce concetti più avanzati, come numeri complessi e immaginari (ad esempio, la radice quadrata di un numero negativo). Sono state create altre interfacce, come IFloatingPointIeee754<TSelf>, poiché 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 a virgola mobile Double implementa IFloatingPointIeee754<TSelf>, ma Int32 non lo fa.
Diverse interfacce sono implementate anche da vari altri tipi, tra i quali Char, DateOnly, DateTime, DateTimeOffset, Decimal, Guid, TimeOnly e TimeSpan.
La seguente tabella 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 di valore zero principali nella rappresentazione binaria. | |
PopCount |
Conta il numero di bit fissi nella rappresentazione binaria. | |
RotateLeft |
Ruota i bit a sinistra (operazione talvolta denominata spostamento circolare a sinistra). | |
RotateRight |
Ruota i bit a destra (operazione talvolta denominata spostamento circolare a destra). | |
TrailingZeroCount |
Conta il numero di bit di coda di valore 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 più piccolo valore rappresentabile 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 di funzione.) | |
INumber<TSelf> | Clamp |
Limita un valore a non più e non meno dei valori minimo e massimo specificati. |
CopySign |
Imposta il segno di un valore specificato come lo stesso di un altro valore specificato. | |
Max |
Restituisce il maggiore tra due valori, restituendo NaN se uno degli input è NaN . |
|
MaxNumber |
Restituisce il maggiore tra due valori, restituendo il numero se uno tra gli input è NaN . |
|
Min |
Restituisce il minore tra due valori, restituendo NaN se uno degli input è NaN . |
|
MinNumber |
Restituisce il minore tra due valori, restituendo il numero se uno tra gli input è NaN . |
|
Sign |
Restituisce -1 per valori negativi, 0 per zero e +1 per valori positivi. | |
INumberBase<TSelf> | One |
Ottiene il valore 1 per il tipo. |
Radix |
Ottiene la base per il 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 è idoneo.1 | |
CreateSaturating |
Crea un valore, bloccando T.MinValue o T.MaxValue se l'input non è idoneo.1 |
|
CreateTruncating |
Crea un valore da un altro valore, se l'input non è idoneo.1 | |
IsComplexNumber |
Restituisce true 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 true e 2.2 restituisce false . |
|
IsFinite |
Restituisce true se il valore non è infinito o NaN . |
|
IsImaginaryNumber |
Restituisce true se il valore ha una parte reale uguale a zero. Ciò significa che 0 è immaginario e 1 + 1i non lo è. |
|
IsInfinity |
Restituisce true se il valore equivale a infinito. | |
IsInteger |
Restituisce true se il valore è un numero intero. 2.0 e 3.0 restituiscono true e 2.2 e 3.1 restituiscono false . |
|
IsNaN |
Restituisce true se il valore rappresenta NaN . |
|
IsNegative |
Restituisce true se il valore è negativo. -0.0. è incluso. | |
IsPositive |
Restituisce true se il valore è positivo. Sono inclusi 0 e +0,0. | |
IsRealNumber |
Restituisce true se il valore ha una parte immaginaria uguale a zero. Ciò significa che 0 è reale, come tutti i tipi INumber<T> . |
|
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 metodi Create*
, considerare i seguenti esempi.
Esempio per quando è stato specificato un valore troppo grande:
byte.CreateChecked(384)
genererà un'eccezione OverflowException.byte.CreateSaturating(384)
restituisce 255 poiché 384 è maggiore di Byte.MaxValue (ovvero 255).byte.CreateTruncating(384)
restituisce 128, poiché prende in considerazione gli 8 bit più bassi (384 ha una rappresentazione esadecimale di0x0180
, e gli 8 bit più bassi sono0x80
, ovvero 128).
Esempio per quando è stato specificato 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, poiché prende in considerazione gli 8 bit più bassi (384 ha una rappresentazione esadecimale di0xFE80
e gli 8 bit più bassi sono0x80
, ovvero 128).
I metodi Create*
prestano anche particolare attenzione ai tipi a virgola mobile IEEE 754 (ad esempio, float
e double
), in quanto hanno i valori speciali PositiveInfinity
, NegativeInfinity
e NaN
. Tutte e tre le API Create*
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
; di conseguenza, si bloccano a questi valori.
Interfacce operatore
Le interfacce operatore corrispondono ai vari operatori disponibili per il linguaggio C#.
- Non associano esplicitamente operazioni come la moltiplicazione e la divisione poiché non risultano corrette per tutti i tipi. Ad esempio,
Matrix4x4 * Matrix4x4
è valido, maMatrix4x4 / Matrix4x4
non lo è. - Generalmente, consentono ai tipi di input e di risultato di differire in modo da supportare scenari quali la divisione di due numeri interi per ottenere un
double
(ad esempio,3 / 2 = 1.5
) o il calcolo della media di una serie di numeri interi.
Nome interfaccia | Operatori definiti |
---|---|
IAdditionOperators<TSelf,TOther,TResult> | x + y |
IBitwiseOperators<TSelf,TOther,TResult> | x & y , 'x | y', x ^ y e ~x |
IComparisonOperators<TSelf,TOther,TResult> | x < y , x > y , x <= y e 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 |
Nota
Alcune interfacce definiscono un operatore controllato oltre al consueto operatore non controllato. Gli operatori controllati vengono chiamati in contesti controllati e consentono a un tipo definito dall'utente di definire il comportamento dell’overflow. Se si implementa un operatore controllato (ad esempio, CheckedSubtraction(TSelf, TOther)), è necessario implementare anche l'operatore non selezionato (ad esempio, Subtraction(TSelf, TOther)).
Interfacce di funzione
Le interfacce di funzione definiscono API matematiche comuni più ampiamente applicabili rispetto a un'interfaccia numerica specifica. Tali interfacce sono tutte implementate da IFloatingPointIeee754<TSelf> e potrebbero in futuro diventare implementate da altri tipi pertinenti.
Nome interfaccia | Descrizione |
---|---|
IExponentialFunctions<TSelf> | Espone funzioni esponenziali che supportano e^x , e^x - 1 , 2^x 2^x - 1 , 10^x e 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 funzioni potenza che supportano x^y . |
IRootFunctions<TSelf> | Espone funzioni radice che supportano cbrt(x) e sqrt(x) . |
ITrigonometricFunctions<TSelf> | Espone 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 di programmazione. Sono comunemente usati per la conversione dell'input dell'utente in un determinato tipo o per mostrare 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) . |
IFormattable1 | Espone il supporto per value.ToString(string, IFormatProvider) . |
ISpanFormattable1 | Espone il supporto per value.TryFormat(Span<char>, out int, ReadOnlySpan<char>, IFormatProvider) . |
1Questa interfaccia non è nuova né generica. Tuttavia, è implementata da tutti i tipi di numeri e rappresenta l'operazione inversa di IParsable
.
Ad esempio, il seguente programma considera due numeri come input, leggendoli dalla console usando un metodo generico in cui il parametro di tipo è vincolato a 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 INumber<TSelf>, e quindi mostra il risultato alla 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
*/