Matemática genérica
O .NET 7 apresenta novas interfaces genéricas relacionadas à matemática à biblioteca de classes base. A disponibilidade dessas interfaces significa que você pode restringir um parâmetro de tipo de um método ou tipo genérico para ser "semelhante a um número". Além disso, o C# 11 e posterior permite definir membros da interface static virtual
. Como os operadores devem ser declarados como static
, esse novo recurso de C# permite que os operadores sejam declarados nas novas interfaces para tipos semelhantes a números.
Juntas, essas inovações permitem executar operações matemáticas genericamente, ou seja, sem precisar saber o tipo exato com o qual você está trabalhando. Por exemplo, se você quisesse escrever um método que adicionasse dois números, anteriormente você precisava adicionar uma sobrecarga do método para cada tipo (por exemplo, static int Add(int first, int second)
e static float Add(float first, float second)
). Agora você pode escrever um único método genérico, em que o parâmetro de tipo é restrito a ser um tipo semelhante a um número. Por exemplo:
static T Add<T>(T left, T right)
where T : INumber<T>
{
return left + right;
}
Nesse método, o parâmetro de tipo T
é restrito a ser um tipo que implementa a nova interface INumber<TSelf>. INumber<TSelf> implementa a interface IAdditionOperators<TSelf,TOther,TResult>, que contém o operador +. Isso permite que o método adicione genericamente os dois números. O método pode ser usado com qualquer um dos tipos numéricos internos do NET, pois todos eles foram atualizados para implementar INumber<TSelf> no .NET 7.
Os autores da biblioteca se beneficiarão mais das interfaces matemáticas genéricas, pois podem simplificar sua base de código removendo sobrecargas "redundantes". Outros desenvolvedores se beneficiarão indiretamente, pois as APIs que consomem podem começar a dar suporte a mais tipos.
As interfaces
As interfaces foram projetadas para serem refinadas o suficiente para que os usuários possam definir as próprias interfaces sobre elas, além de serem granulares o suficiente para serem fáceis de consumir. Nessa medida, há algumas interfaces numéricas principais com as quais a maioria dos usuários interagirá, como INumber<TSelf> e IBinaryInteger<TSelf>. As interfaces mais refinadas, como IAdditionOperators<TSelf,TOther,TResult> e ITrigonometricFunctions<TSelf>, dão suporte a esses tipos e estão disponíveis para desenvolvedores que definem as próprias interfaces numéricas específicas do domínio.
Interfaces numéricas
Esta seção descreve as interfaces em System.Numerics que descrevem tipos semelhantes a números e a funcionalidade disponível para eles.
Nome da interface | Descrição |
---|---|
IBinaryFloatingPointIeee754<TSelf> | Expõe APIs comuns aos tipos de ponto flutuante binário1 que implementam o padrão IEEE 754. |
IBinaryInteger<TSelf> | Expõe APIs comuns a inteiros binários2. |
IBinaryNumber<TSelf> | Expõe APIs comuns a números binários. |
IFloatingPoint<TSelf> | Expõe APIs comuns a tipos de ponto flutuante. |
IFloatingPointIeee754<TSelf> | Expõe APIs comuns aos tipos de ponto flutuante que implementam o padrão IEEE 754. |
INumber<TSelf> | Expõe APIs comuns a tipos de número comparáveis (efetivamente, o domínio numérico "real"). |
INumberBase<TSelf> | Expõe APIs comuns a todos os tipos de número (efetivamente, o domínio numérico "complexo"). |
ISignedNumber<TSelf> | Expõe APIs comuns a todos os tipos de número com sinal (como o conceito de NegativeOne ). |
IUnsignedNumber<TSelf> | Expõe APIs comuns a todos os tipos de número sem sinal. |
IAdditiveIdentity<TSelf,TResult> | Expõe o conceito de (x + T.AdditiveIdentity) == x . |
IMinMaxValue<TSelf> | Expõe o conceito de T.MinValue e T.MaxValue . |
IMultiplicativeIdentity<TSelf,TResult> | Expõe o conceito de (x * T.MultiplicativeIdentity) == x . |
1Os tipos de ponto flutuante binários são Double (double
), Half e Single (float
).
2Os tipos inteiros binários são Byte (byte
), Int16 (short
), Int32 (int
), Int64 (long
), Int128, IntPtr (nint
), SByte (sbyte
), UInt16 (ushort
), UInt32 (uint
), UInt64 (ulong
), UInt128 e UIntPtr (nuint
).
A interface que você provavelmente usará diretamente é INumber<TSelf>, que corresponde aproximadamente a um número real. Se um tipo implementar essa interface, isso significa que um valor tem um sinal (isso inclui tipos unsigned
, que são considerados positivos) e pode ser comparado a outros valores do mesmo tipo. INumberBase<TSelf> confere conceitos mais avançados, como números complexos e imaginários, por exemplo, a raiz quadrada de um número negativo. Outras interfaces, como IFloatingPointIeee754<TSelf>, foram criadas porque nem todas as operações fazem sentido para todos os tipos de número — por exemplo, calcular o piso de um número só faz sentido para tipos de ponto flutuante. Na biblioteca de classes base do .NET, o tipo de ponto flutuante Double implementa IFloatingPointIeee754<TSelf>, mas Int32 não implementa.
Várias das interfaces também são implementadas por vários outros tipos, incluindo Char, DateOnly, DateTime, DateTimeOffset, Decimal, Guid, TimeOnly e TimeSpan.
A tabela a seguir mostra algumas das APIs principais expostas por cada interface.
Interface | Nome da API | Descrição |
---|---|---|
IBinaryInteger<TSelf> | DivRem |
Calcula o quociente e o resto simultaneamente. |
LeadingZeroCount |
Conta o número de bits zero à esquerda na representação binária. | |
PopCount |
Conta o número de bits definidos na representação binária. | |
RotateLeft |
Gira bits para a esquerda, às vezes também chamado de deslocamento circular à esquerda. | |
RotateRight |
Gira bits para a direita, às vezes também chamado de deslocamento circular para a direita. | |
TrailingZeroCount |
Conta o número de bits zero à direita na representação binária. | |
IFloatingPoint<TSelf> | Ceiling |
Arredonda o valor para o infinito positivo. +4,5 torna-se +5 e -4,5 torna-se -4. |
Floor |
Arredonda o valor para o infinito negativo. +4,5 torna-se +4 e -4,5 torna-se -5. | |
Round |
Arredonda o valor usando o modo de arredondamento especificado. | |
Truncate |
Arredonda o valor para zero. +4,5 torna-se +4 e -4,5 torna-se -4. | |
IFloatingPointIeee754<TSelf> | E |
Obtém um valor que representa o número de Euler para o tipo. |
Epsilon |
Obtém o menor valor representável que é maior que zero para o tipo. | |
NaN |
Obtém um valor que representa NaN para o tipo. |
|
NegativeInfinity |
Obtém um valor que representa -Infinity para o tipo. |
|
NegativeZero |
Obtém um valor que representa -Zero para o tipo. |
|
Pi |
Obtém um valor que representa Pi para o tipo. |
|
PositiveInfinity |
Obtém um valor que representa +Infinity para o tipo. |
|
Tau |
Obtém um valor que representa Tau (2 * Pi ) para o tipo. |
|
(Outros) | (Implementa o conjunto completo de interfaces listadas em Interfaces de função.) | |
INumber<TSelf> | Clamp |
Restringe um valor a nada mais e nada menos que o valor mínimo e máximo especificados. |
CopySign |
Define o sinal de um valor especificado como o mesmo que de outro valor especificado. | |
Max |
Retorna o maior de dois valores, retornando NaN se uma das entradas for NaN . |
|
MaxNumber |
Retorna o maior de dois valores, retornando o número se uma entrada for NaN . |
|
Min |
Retorna o menor de dois valores, retornando NaN se uma das entradas for NaN . |
|
MinNumber |
Retorna o menor de dois valores, retornando o número se uma entrada for NaN . |
|
Sign |
Retorna -1 para valores negativos, 0 para zero e +1 para valores positivos. | |
INumberBase<TSelf> | One |
Obtém o valor 1 para o tipo. |
Radix |
Obtém a base para o tipo. Int32 retorna 2. Decimal retorna 10. | |
Zero |
Obtém o valor 0 para o tipo. | |
CreateChecked |
Cria um valor, gerando um OverflowException se a entrada não puder se encaixar.1 | |
CreateSaturating |
Cria um valor, fixando a T.MinValue ou T.MaxValue se a entrada não puder se encaixar.1 |
|
CreateTruncating |
Cria um valor com base em outro valor, encapsulando se a entrada não puder se encaixar. 1 | |
IsComplexNumber |
Retornará true se o valor tiver uma parte real diferente de zero e uma parte imaginária diferente de zero. | |
IsEvenInteger |
Retornará true se um valor for um inteiro par. 2.0 retorna true e 2.2 retorna false . |
|
IsFinite |
Retornará true se o valor não for infinito e não for NaN . |
|
IsImaginaryNumber |
Retornará true se o valor tiver uma parte real zero. Isso significa que 0 é imaginário e 1 + 1i não é. |
|
IsInfinity |
Retornará true se o valor representar infinito. | |
IsInteger |
Retornará true se um valor for um inteiro. 2.0 e 3.0 retornam true e 2.2 e 3.1 retornam false . |
|
IsNaN |
Retornará true se o valor representar NaN . |
|
IsNegative |
Retornará true se o valor for negativo. Isso inclui -0.0. | |
IsPositive |
Retornará true se o valor for positivo. Isso inclui 0 e +0.0. | |
IsRealNumber |
Retornará true se o valor tiver uma parte imaginária zero. Isso significa que 0 é real, assim como todos os tipos INumber<T> . |
|
IsZero |
Retornará true se o valor representar zero. Isso inclui 0, +0.0 e -0.0. | |
MaxMagnitude |
Retorna o valor com um valor absoluto maior, retornando NaN se uma das entradas for NaN . |
|
MaxMagnitudeNumber |
Retorna o valor com um valor absoluto maior, retornando o número se uma entrada for NaN . |
|
MinMagnitude |
Retorna o valor com um valor absoluto menor, retornando NaN se uma das entradas for NaN . |
|
MinMagnitudeNumber |
Retorna o valor com um valor absoluto menor, retornando o número se uma entrada for NaN . |
|
ISignedNumber<TSelf> | NegativeOne |
Obtém o valor -1 para o tipo. |
1Para ajudar a entender o comportamento dos três métodos de Create*
, considere os exemplos a seguir.
Exemplo quando dado um valor muito grande:
byte.CreateChecked(384)
gerará um OverflowException.byte.CreateSaturating(384)
retorna 255 porque 384 é maior que Byte.MaxValue (que é 255).byte.CreateTruncating(384)
retorna 128 porque leva os 8 bits mais baixos (384 tem uma representação hexadecimal de0x0180
, e os 8 bits mais baixos são0x80
, que é 128).
Exemplo quando dado um valor muito pequeno:
byte.CreateChecked(-384)
gerará um OverflowException.byte.CreateSaturating(-384)
retorna 0 porque -384 é menor que Byte.MinValue (que é 0).byte.CreateTruncating(-384)
retorna 128 porque leva os 8 bits mais baixos (384 tem uma representação hexadecimal de0xFE80
, e os 8 bits mais baixos são0x80
, que é 128).
Os métodos Create*
também têm algumas considerações especiais para tipos de ponto flutuante IEEE 754, como float
e double
, pois eles têm os valores especiais PositiveInfinity
, NegativeInfinity
e NaN
. Todas as três APIs Create*
se comportam como CreateSaturating
. Além disso, enquanto MinValue
e MaxValue
representam o maior número "normal" negativo/positivo, os valores mínimo e máximo reais são NegativeInfinity
e PositiveInfinity
, portanto, eles se prendem a esses valores.
Interfaces de operador
As interfaces do operador correspondem aos vários operadores disponíveis para a linguagem C#.
- Eles não emparelham explicitamente operações como multiplicação e divisão, pois isso não é correto para todos os tipos. Por exemplo,
Matrix4x4 * Matrix4x4
é válido, masMatrix4x4 / Matrix4x4
não é válido. - Normalmente, eles permitem que os tipos de entrada e de resultado sejam diferentes para dar suporte a cenários como dividir dois inteiros para obter um
double
, por exemplo,3 / 2 = 1.5
, ou calcular a média de um conjunto de inteiros.
Nome da interface | Operadores definidos |
---|---|
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 |
Observação
Algumas das interfaces definem um operador verificado, além de um operador não verificado regular. Os operadores verificados são chamados em contextos verificados e permitem que um tipo definido pelo usuário defina o comportamento de estouro. Se você implementar um operador verificado, por exemplo, CheckedSubtraction(TSelf, TOther), também deverá implementar o operador não verificado, por exemplo, Subtraction(TSelf, TOther).
Interfaces de função
As interfaces de função definem APIs matemáticas comuns que se aplicam mais amplamente do que a uma interface numérica específica. Essas interfaces são implementadas por IFloatingPointIeee754<TSelf> e podem ser implementadas por outros tipos relevantes no futuro.
Nome da interface | Descrição |
---|---|
IExponentialFunctions<TSelf> | Expõe funções exponenciais que dão suporte a e^x , e^x - 1 , 2^x , 2^x - 1 , 10^x e 10^x - 1 . |
IHyperbolicFunctions<TSelf> | Expõe funções hiperbólicas que dão suporte a acosh(x) , asinh(x) , atanh(x) , cosh(x) , sinh(x) e tanh(x) . |
ILogarithmicFunctions<TSelf> | Expõe funções logarítmicas com suporte a ln(x) , ln(x + 1) , log2(x) , log2(x + 1) , log10(x) e log10(x + 1) . |
IPowerFunctions<TSelf> | Expõe funções de potência que dão suporte a x^y . |
IRootFunctions<TSelf> | Expõe funções raiz que dão suporte a cbrt(x) e sqrt(x) . |
ITrigonometricFunctions<TSelf> | Expõe funções trigonométricas que dão suporte a acos(x) , asin(x) , atan(x) , cos(x) , sin(x) e tan(x) . |
Interfaces de análise e formatação
Análise e formatação são conceitos fundamentais na programação. Eles são comumente usados ao converter a entrada do usuário em um determinado tipo ou exibir um tipo para o usuário. Essas interfaces estão no namespace System.
Nome da interface | Descrição |
---|---|
IParsable<TSelf> | Expõe o suporte para T.Parse(string, IFormatProvider) e T.TryParse(string, IFormatProvider, out TSelf) . |
ISpanParsable<TSelf> | Expõe o suporte para T.Parse(ReadOnlySpan<char>, IFormatProvider) e T.TryParse(ReadOnlySpan<char>, IFormatProvider, out TSelf) . |
IFormattable1 | Expõe o suporte para value.ToString(string, IFormatProvider) . |
ISpanFormattable1 | Expõe o suporte para value.TryFormat(Span<char>, out int, ReadOnlySpan<char>, IFormatProvider) . |
1 Essa interface não é nova nem genérica. No entanto, ela é implementada por todos os tipos de número e representa a operação inversa de IParsable
.
Por exemplo, o programa a seguir usa dois números como entrada, lendo-os do console usando um método genérico em que o parâmetro de tipo é restrito a ser IParsable<TSelf>. Ele calcula a média usando um método genérico em que os parâmetros de tipo para os valores de entrada e resultado são restritos a ser INumber<TSelf> e, em seguida, exibe o resultado para o 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
*/