Compartilhar via


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 tipo ou método genérico para ser "semelhante a número". Além disso, o C# 11 e posterior permite definir static virtual 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 você está trabalhando. Por exemplo, se você quisesse escrever um método que adiciona dois números, anteriormente 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, 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 T de tipo é 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 tipos numéricos internos de .NET, porque 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 consumidas 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 suas 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 a 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ú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.

1Os tipos de ponto flutuante binário são Double (double) Halfe 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 unsigned tipos, 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úmeros, 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 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, e TimeOnlyTimeSpan.

A tabela a seguir mostra algumas das APIs principais expostas por cada interface.

Interfase Nome da API Descrição
IBinaryInteger<TSelf> DivRem Calcula o quociente e o resto simultaneamente.
LeadingZeroCount Conta o número de bits zero iniciais 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 em direção ao 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 não mais e nada menos que o valor mínimo e máximo 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 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 Retorna verdadeiro se o valor for um inteiro par. 2.0 retorna truee 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 Retorna verdadeiro se o valor for um inteiro. 2.0 e 3.0 retornam truee 2.2 e 3.1 retornam false.
IsNaN Retornará true se o valor representar NaN.
IsNegative Retorna verdadeiro se o valor é negativo. Isso inclui -0.0.
IsPositive Retorna verdadeiro 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 INumber<T> tipos.
IsZero Retorna verdadeiro se o valor representa 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 Create* métodos, 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 hexadecima de 0x0180, e os 8 bits mais baixos são 0x80, 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 (o que é 0).
  • byte.CreateTruncating(-384) retorna 128 porque leva os 8 bits mais baixos (384 tem uma representação hexadecima 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, pois possuem os valores especiais PositiveInfinity, NegativeInfinity e NaN. Todas as três Create* APIs se comportam como CreateSaturating. Além disso, enquanto MinValue e MaxValue representam o maior número "normal" negativo e positivo, os valores mínimo e máximo reais são NegativeInfinity e PositiveInfinity, portanto, eles se limitam a esses valores.

Interfaces de operador

As interfaces do operador correspondem aos vários operadores disponíveis para o idioma C#.

  • Eles explicitamente não emparelham operações como multiplicação e divisão, pois isso não está correto para todos os tipos. Por exemplo, Matrix4x4 * Matrix4x4 é válido, mas Matrix4x4 / Matrix4x4 não é válido.
  • Normalmente, eles permitem que os tipos de entrada e de resultado diferem para dar suporte a cenários como dividir 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 <= ye 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 superação de capacidade. 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 todas 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 com 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 suportando acosh(x), asinh(x), atanh(x), cosh(x), sinh(x) e tanh(x).
ILogarithmicFunctions<TSelf> Expõe funções logarítmicas com suporte 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 com suporte cbrt(x) e sqrt(x).
ITrigonometricFunctions<TSelf> Expõe funções trigonométricas que oferecem 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 System namespace.

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).
IFormattable 1 Expõe o suporte para value.ToString(string, IFormatProvider).
ISpanFormattable 1 Expõe o suporte para value.TryFormat(Span<char>, out int, ReadOnlySpan<char>, IFormatProvider).

1Essa interface não é nova, nem é genérica. No entanto, ele é implementado 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
*/

Consulte também