Condividi tramite


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 virtualmembri 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

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 di 0x0180, e gli 8 bit più bassi sono 0x80, 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 di 0xFE80 e gli 8 bit più bassi sono 0x80, 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, ma Matrix4x4 / 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^x2^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
*/

Vedi anche