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.MinValueT.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 de 0x0180, e os 8 bits mais baixos são 0x80, 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 de 0xFE80, e os 8 bits mais baixos são 0x80, 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 PositiveInfinityespeciais , 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, mas Matrix4x4 / 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.5ou 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 ^ ye ~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^xe 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
*/

Consulte também