Matemática genérica
O .NET 7 introduz novas interfaces genéricas relacionadas à matemática para a biblioteca de classes base. A disponibilidade dessas interfaces significa que você pode restringir um parâmetro de tipo de um tipo genérico ou método a ser "semelhante a um número". Além disso, o C# 11 e posterior permite definir static virtual
os membros da interface. Como os operadores devem ser declarados como static
, esse novo recurso C# permite que os operadores sejam declarados nas novas interfaces para tipos semelhantes a números.
Juntas, essas inovações permitem que você execute operações matemáticas de forma genérica, ou seja, sem precisar saber o tipo exato com o qual está trabalhando. Por exemplo, se você quisesse escrever um método que adicionasse dois números, anteriormente você tinha que 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, onde o parâmetro type é 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 T
type é restrito a ser um tipo que implementa a nova INumber<TSelf> interface. INumber<TSelf> Implementa a IAdditionOperators<TSelf,TOther,TResult> interface, 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 arquivos . Tipos numéricos internos da NET, porque todos eles foram atualizados para implementar INumber<TSelf> no .NET 7.
Os autores de bibliotecas serão os que mais se beneficiarão das interfaces matemáticas genéricas, porque podem simplificar sua base de código removendo sobrecargas "redundantes". Outros desenvolvedores se beneficiarão indiretamente, porque as APIs que consomem podem começar a oferecer suporte a mais tipos.
As interfaces
As interfaces foram projetadas para serem refinadas o suficiente para que os usuários possam definir suas próprias interfaces no topo, além de serem granulares o suficiente para serem fáceis de consumir. Nessa medida, existem algumas interfaces numéricas centrais com as quais a maioria dos usuários irá interagir, como INumber<TSelf> e IBinaryInteger<TSelf>. As interfaces mais refinadas, como IAdditionOperators<TSelf,TOther,TResult> e ITrigonometricFunctions<TSelf>, suportam esses tipos e estão disponíveis para desenvolvedores que definem suas próprias interfaces numéricas específicas de domínio.
Interfaces numéricas
Esta seção descreve as interfaces que System.Numerics descrevem tipos semelhantes a números e a funcionalidade disponível para eles.
Nome da interface | Description |
---|---|
IBinaryFloatingPointIeee754<TSelf> | Expõe APIs comuns aos tiposbinários de ponto flutuante 1 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 a tipos de ponto flutuante que implementam o padrão IEEE 754. |
INumber<TSelf> | Expõe APIs comuns a tipos de números comparáveis (efetivamente o domínio de número "real"). |
INumberBase<TSelf> | Expõe APIs comuns a todos os tipos de números (efetivamente o domínio numérico "complexo"). |
ISignedNumber<TSelf> | Expõe APIs comuns a todos os tipos de números assinados (como o conceito de NegativeOne ). |
IUnsignedNumber<TSelf> | Expõe APIs comuns a todos os tipos de números não assinados. |
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 . |
1 Os tipos binários de vírgula flutuante são Double (double
), Halfe Single (float
).
2 Os tipos inteiros binários são Byte (byte
), Int16 (short
), Int32 (int
), Int64 (long
), Int128IntPtr , (nint
), SByte (sbyte
), (ushort
), UInt16 (), UInt32 (uint
), UInt128UIntPtrUInt64 eulong
().nuint
A interface que você provavelmente usará diretamente é INumber<TSelf>, que corresponde aproximadamente a um número real . Se um tipo implementa essa interface, isso significa que um valor tem um sinal (isso inclui unsigned
tipos, que são considerados positivos) e pode ser comparado com 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úmeros — por exemplo, calcular o piso de um número só faz sentido para tipos de vírgula flutuante. Na biblioteca de classes base do .NET, o tipo Double de ponto flutuante 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, TimeOnlye TimeSpan.
A tabela a seguir mostra algumas das principais APIs expostas por cada interface.
Interface | Nome da API | Description |
---|---|---|
IBinaryInteger<TSelf> | DivRem |
Calcula o quociente e o restante simultaneamente. |
LeadingZeroCount |
Conta o número de zero bits à 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 para a 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 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 não mais nem menos do que o valor min e max especificado. |
CopySign |
Define o sinal de um valor especificado como o mesmo que outro valor especificado. | |
Max |
Retorna o maior de dois valores, retornando NaN se qualquer entrada 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 qualquer entrada 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 o radix, ou base, para o tipo. Int32 devoluções 2. Decimal devolve 10. | |
Zero |
Obtém o valor 0 para o tipo. | |
CreateChecked |
Cria um valor, lançando um OverflowException se a entrada não se encaixar.1 | |
CreateSaturating |
Cria um valor, fixando ou T.MinValue T.MaxValue se a entrada não puder ajustar.1 |
|
CreateTruncating |
Cria um valor a partir de outro valor, envolvendo se a entrada não puder ajustar.1 | |
IsComplexNumber |
Retorna true se o valor tiver uma parte real diferente de zero e uma parte imaginária diferente de zero. | |
IsEvenInteger |
Retorna true se o valor for um inteiro par. 2.0 retorna true , e 2.2 retorna false . |
|
IsFinite |
Retorna true se o valor não for infinito e não NaN . |
|
IsImaginaryNumber |
Retorna true se o valor tiver uma parte real zero. Este meio 0 é imaginário e 1 + 1i não é. |
|
IsInfinity |
Retorna true se o valor representar infinito. | |
IsInteger |
Retorna true se o valor for um inteiro. 2.0 e 3.0 retorno true , e 2.2 e 3.1 retorno false . |
|
IsNaN |
Retorna true se o valor representar NaN . |
|
IsNegative |
Retorna true se o valor for negativo. Isso inclui -0,0. | |
IsPositive |
Retorna true se o valor for positivo. Isso inclui 0 e +0,0. | |
IsRealNumber |
Retorna true se o valor tiver uma parte imaginária zero. Isso significa que 0 é real, assim como todos os INumber<T> tipos. |
|
IsZero |
Retorna 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 qualquer entrada 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 qualquer entrada 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. |
1 Para ajudar a entender o comportamento dos três Create*
métodos, considere os exemplos a seguir.
Exemplo quando é dado um valor muito grande:
byte.CreateChecked(384)
vai lançar 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)
vai lançar 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 Create*
métodos também têm algumas considerações especiais para tipos de ponto flutuante IEEE 754, como float
e double
, como eles têm os valores PositiveInfinity
especiais , NegativeInfinity
, e NaN
. Todas as três Create*
APIs se comportam como CreateSaturating
. Além disso, embora MinValue
representem MaxValue
o maior número "normal" negativo/positivo, os valores mínimos e máximos reais são NegativeInfinity
e PositiveInfinity
, por isso agarram-se a estes valores.
Interfaces do operador
As interfaces do operador correspondem aos vários operadores disponíveis para a linguagem C#.
- Eles explicitamente não emparelham 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. - Eles normalmente permitem que os tipos de entrada e resultado sejam diferentes para dar suporte a cenários como a divisão de 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 |
Nota
Algumas das interfaces definem um operador verificado, além de um operador regular não verificado. 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)você também deve 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 de forma mais ampla do que a uma interface numérica específica. Essas interfaces são todas implementadas pela IFloatingPointIeee754<TSelf>, e podem ser implementadas por outros tipos relevantes no futuro.
Nome da interface | Description |
---|---|
IExponentialFunctions<TSelf> | Expõe funções exponenciais que suportam 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 suportam acosh(x) , asinh(x) , , atanh(x) cosh(x) , sinh(x) e tanh(x) . |
ILogarithmicFunctions<TSelf> | Expõe funções logarítmicas que suportam ln(x) , ln(x + 1) , log2(x) , log2(x + 1) , log10(x) e log10(x + 1) . |
IPowerFunctions<TSelf> | Expõe funções de alimentação que suportam x^y . |
IRootFunctions<TSelf> | Expõe funções raiz que suportam cbrt(x) e sqrt(x) . |
ITrigonometricFunctions<TSelf> | Expõe funções trigonométricas que suportam acos(x) , asin(x) , , cos(x) atan(x) , sin(x) e tan(x) . |
Interfaces de análise e formatação
Análise e formatação são conceitos centrais na programação. Eles são comumente usados ao converter a entrada do usuário para um determinado tipo ou exibir um tipo para o usuário. Essas interfaces estão no System namespace.
Nome da interface | Description |
---|---|
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 Esta interface não é nova, nem genérica. No entanto, ele é implementado por todos os tipos de números e representa a operação inversa do IParsable
.
Por exemplo, o programa a seguir usa dois números como entrada, lendo-os do console usando um método genérico onde o parâmetro type é restrito a ser IParsable<TSelf>. Ele calcula a média usando um método genérico onde 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
*/