Compartir a través de


Matemáticas genéricas

.NET 7 presenta nuevas interfaces genéricas relacionadas con matemáticas en la biblioteca de clases base. La existencia de estas interfaces significa que puede restringir un parámetro de tipo de un tipo genérico o método para que sea "de tipo numérico". Además, C# 11 y versiones posteriores le permiten definir static virtual miembros de interfaz. Dado que los operadores deben declararse como static, esta nueva característica de C# permite que los operadores se declaren en las nuevas interfaces para tipos de tipo numérico.

Juntas, estas innovaciones le permiten realizar operaciones matemáticas genéricamente, es decir, sin tener que conocer el tipo exacto con el que trabaja. Por ejemplo, si desea escribir un método que agrega dos números, anteriormente tenía que agregar una sobrecarga del método para cada tipo (por ejemplo, static int Add(int first, int second) y static float Add(float first, float second)). Ahora puede escribir un único método genérico, donde el parámetro de tipo está restringido para que sea un tipo similar a un número. Por ejemplo:

static T Add<T>(T left, T right)
    where T : INumber<T>
{
    return left + right;
}

En este método, el parámetro T de tipo está restringido para que sea un tipo que implemente la nueva INumber<TSelf> interfaz. INumber<TSelf> implementa la IAdditionOperators<TSelf,TOther,TResult> interfaz , que contiene el operador + . Esto permite que el método agregue genéricamente los dos números. El método se puede usar con cualquiera de los tipos numéricos integrados de .NET, ya que todos se han actualizado para implementar INumber<TSelf> en .NET 7.

Los autores de bibliotecas se beneficiarán la mayoría de las interfaces matemáticas genéricas, ya que pueden simplificar su base de código mediante la eliminación de sobrecargas "redundantes". Otros desarrolladores se beneficiarán indirectamente, ya que las API que consumen pueden empezar a admitir más tipos.

Las interfaces

Las interfaces se han diseñado para ser lo suficientemente específicas como para que los usuarios puedan definir sus propias interfaces a partir de ellas, al tiempo que son lo suficientemente pormenorizadas como para que sean fáciles de consumir. En esa medida, hay algunas interfaces numéricas básicas con las que la mayoría de los usuarios interactuarán, como INumber<TSelf> y IBinaryInteger<TSelf>. Las interfaces más específicas, como IAdditionOperators<TSelf,TOther,TResult> y ITrigonometricFunctions<TSelf>, admiten estos tipos y están disponibles para los desarrolladores que definen sus propias interfaces numéricas específicas del dominio.

Interfaces numéricas

En esta sección se describen las interfaces en System.Numerics que describen tipos similares a números y la funcionalidad disponible para ellos.

Nombre de la interfaz Descripción
IBinaryFloatingPointIeee754<TSelf> Expone las API comunes a los tipos de punto flotantebinarios 1 que implementan el estándar IEEE 754.
IBinaryInteger<TSelf> Expone las API comunes a enteros binarios2.
IBinaryNumber<TSelf> Expone las API comunes a los números binarios.
IFloatingPoint<TSelf> Expone las API comunes a los tipos de punto flotante.
IFloatingPointIeee754<TSelf> Expone las API comunes a los tipos de punto flotante que implementan el estándar IEEE 754.
INumber<TSelf> Expone las API comunes a tipos de números comparables (efectivamente el dominio de número "real").
INumberBase<TSelf> Expone las API comunes a todos los tipos de números (efectivamente el dominio de número "complejo").
ISignedNumber<TSelf> Expone las API comunes a todos los tipos de números firmados (como el concepto de NegativeOne).
IUnsignedNumber<TSelf> Expone las API comunes a todos los tipos de números sin firmar.
IAdditiveIdentity<TSelf,TResult> Expone el concepto de (x + T.AdditiveIdentity) == x.
IMinMaxValue<TSelf> Expone el concepto de T.MinValue y T.MaxValue.
IMultiplicativeIdentity<TSelf,TResult> Expone el concepto de (x * T.MultiplicativeIdentity) == x.

1Los tipos de punto flotante binario son Double (double), Halfy Single (float).

2Los tipos enteros binarios son Byte (byte), Int16 (short), Int32 (int), Int64 (long), Int128, IntPtr (nint), SByte (sbyte), UInt16 (ushort), UInt32 (uint), UInt64 (ulong), UInt128, y UIntPtr (nuint).

La interfaz que es más probable que use directamente es INumber<TSelf>, que aproximadamente corresponde a un número real . Si un tipo implementa esta interfaz, significa que un valor tiene un signo (esto incluye unsigned tipos, que se consideran positivos) y se puede comparar con otros valores del mismo tipo. INumberBase<TSelf> confiere conceptos más avanzados, como números complejos e imaginarios , por ejemplo, la raíz cuadrada de un número negativo. Otras interfaces, como IFloatingPointIeee754<TSelf>, se crearon porque no todas las operaciones tienen sentido para todos los tipos de números; por ejemplo, calcular el suelo de un número solo tiene sentido para los tipos de punto flotante. En la biblioteca de clases base de .NET, el tipo Double de punto flotante implementa IFloatingPointIeee754<TSelf> pero Int32 no.

Varias de las interfaces también se implementan mediante otros tipos, como Char, DateOnly, DateTimeDateTimeOffset, , Decimal, Guidy TimeOnlyTimeSpan.

En la tabla siguiente se muestran algunas de las API principales expuestas por cada interfaz.

Interfaz Nombre de la API Descripción
IBinaryInteger<TSelf> DivRem Calcula el cociente y el resto simultáneamente.
LeadingZeroCount Cuenta el número de bits cero iniciales en la representación binaria.
PopCount Cuenta el número de bits establecidos en la representación binaria.
RotateLeft Gira los bits a la izquierda, a veces también denominado desplazamiento circular a la izquierda.
RotateRight Gira los bits a la derecha, a veces también denominado desplazamiento circular a la derecha.
TrailingZeroCount Cuenta el número de bits finales cero en la representación binaria.
IFloatingPoint<TSelf> Ceiling Redondea el valor hacia el infinito positivo. +4.5 se convierte en +5 y -4.5 se convierte en -4.
Floor Redondea el valor hacia el infinito negativo. +4.5 se convierte en +4 y -4.5 se convierte en -5.
Round Redondea el valor mediante el modo de redondeo especificado.
Truncate Redondea el valor hacia cero. +4.5 se convierte en +4 y -4.5 se convierte en -4.
IFloatingPointIeee754<TSelf> E Obtiene un valor que representa el número de Euler para el tipo.
Epsilon Obtiene el valor representable más pequeño que es mayor que cero para el tipo.
NaN Obtiene un valor que representa NaN para el tipo.
NegativeInfinity Obtiene un valor que representa -Infinity para el tipo.
NegativeZero Obtiene un valor que representa -Zero para el tipo.
Pi Obtiene un valor que representa Pi para el tipo.
PositiveInfinity Obtiene un valor que representa +Infinity para el tipo.
Tau Obtiene un valor que representa Tau (2 * Pi) para el tipo.
(Otros) (Implementa el conjunto completo de interfaces enumeradas en Interfaces de función).
INumber<TSelf> Clamp Restringe un valor a no más ni menos que el valor mínimo y máximo especificados.
CopySign Establece el signo de un valor especificado en el mismo que otro valor especificado.
Max Devuelve el mayor de dos valores, devolviendo NaN si alguna de las entradas es NaN.
MaxNumber Devuelve el mayor de dos valores y devuelve el número si una entrada es NaN.
Min Devuelve el menor de dos valores, devolviendo NaN si alguna de las entradas es NaN.
MinNumber Devuelve el menor de dos valores y devuelve el número si una entrada es NaN.
Sign Devuelve -1 para valores negativos, 0 para cero y +1 para valores positivos.
INumberBase<TSelf> One Obtiene el valor 1 para el tipo.
Radix Obtiene la raíz, o base, para el tipo. Int32 devuelve 2. Decimal devuelve 10.
Zero Obtiene el valor 0 para el tipo.
CreateChecked Crea un valor y lanza un OverflowException si la entrada no se puede acomodar.1
CreateSaturating Crea un valor sujeto a T.MinValue o T.MaxValue si la entrada no cabe1.
CreateTruncating Crea un valor a partir de otro valor, encapsulando si la entrada no se puede ajustar. 1
IsComplexNumber Devuelve true si el valor tiene una parte real distinta de cero y una parte imaginaria distinta de cero.
IsEvenInteger Devuelve true si el valor es un entero par. 2.0 devuelve truey 2.2 devuelve false.
IsFinite Devuelve verdadero si el valor no es infinito y no es NaN.
IsImaginaryNumber Devuelve true si el valor tiene una parte real cero. Esto significa 0 que es imaginario y 1 + 1i no lo es.
IsInfinity Devuelve true si el valor representa infinito.
IsInteger Devuelve true si el valor es un entero. 2.0 y 3.0 devuelven true, y 2.2 y 3.1 devuelven false.
IsNaN Devuelve true si el valor representa NaN.
IsNegative Devuelve true si el valor es negativo. Esto incluye -0.0.
IsPositive Devuelve true si el valor es positivo. Esto incluye 0 y +0.0.
IsRealNumber Devuelve true si el valor tiene una parte imaginaria cero. Esto significa que 0 es real, al igual que todos los tipos INumber<T>.
IsZero Devuelve true si el valor representa cero. Esto incluye 0, +0.0 y -0.0.
MaxMagnitude Devuelve el valor con un valor absoluto mayor, devolviendo NaN si alguna de las entradas es NaN.
MaxMagnitudeNumber Devuelve el valor con un valor absoluto mayor y devuelve el número si una entrada es NaN.
MinMagnitude Devuelve el valor con un valor absoluto menor, devolviendo NaN si alguna de las entradas es NaN.
MinMagnitudeNumber Devuelve el valor con un valor absoluto menor, devolviendo el número si una entrada es NaN.
ISignedNumber<TSelf> NegativeOne Obtiene el valor -1 para el tipo.

1Para ayudar a comprender el comportamiento de los tres Create* métodos, tenga en cuenta los ejemplos siguientes.

Ejemplo cuando se le asigna un valor demasiado grande:

  • byte.CreateChecked(384) produce una excepción OverflowException.
  • byte.CreateSaturating(384) devuelve 255 porque 384 es mayor que Byte.MaxValue (que es 255).
  • byte.CreateTruncating(384) devuelve 128 porque toma los 8 bits más bajos (384 tiene una representación hexadecimal de 0x0180 y los 8 bits más bajos son 0x80, que es 128).

Ejemplo cuando se le asigna un valor demasiado pequeño:

  • byte.CreateChecked(-384) produce una excepción OverflowException.
  • byte.CreateSaturating(-384) devuelve 0 porque -384 es menor que Byte.MinValue (que es 0).
  • byte.CreateTruncating(-384) devuelve 128 porque toma los 8 bits más bajos (384 tiene una representación hexadecimal de 0xFE80 y los 8 bits más bajos son 0x80, que es 128).

Los Create* métodos también tienen algunas consideraciones especiales para los tipos de punto flotante IEEE 754, como float y double, ya que tienen los valores especiales PositiveInfinity, NegativeInfinity, y NaN. Las tres Create* API se comportan como CreateSaturating. Además, mientras que MinValue y MaxValue representan el mayor número "normal" negativo/positivo, los valores mínimo y máximo reales son NegativeInfinity y PositiveInfinity, por lo que se fijan en estos valores en su lugar.

Interfaces de operador

Las interfaces de operador corresponden a los distintos operadores disponibles para el lenguaje C#.

  • No emparejan explícitamente operaciones como la multiplicación y división, ya que eso no es correcto para todos los tipos. Por ejemplo, Matrix4x4 * Matrix4x4 es válido, pero Matrix4x4 / Matrix4x4 no es válido.
  • Normalmente permiten que los tipos de entrada y resultado sean diferentes para admitir escenarios como dividir dos enteros para obtener un double, por ejemplo, 3 / 2 = 1.5o calcular el promedio de un conjunto de enteros.
Nombre de la interfaz Operadores definidos
IAdditionOperators<TSelf,TOther,TResult> x + y
IBitwiseOperators<TSelf,TOther,TResult> x & y, 'x | y', x ^ y, y ~x
IComparisonOperators<TSelf,TOther,TResult> x < y, x > y, x <= yy x >= y
IDecrementOperators<TSelf> --x y x--
IDivisionOperators<TSelf,TOther,TResult> x / y
IEqualityOperators<TSelf,TOther,TResult> x == y y x != y
IIncrementOperators<TSelf> ++x y x++
IModulusOperators<TSelf,TOther,TResult> x % y
IMultiplyOperators<TSelf,TOther,TResult> x * y
IShiftOperators<TSelf,TOther,TResult> x << y y x >> y
ISubtractionOperators<TSelf,TOther,TResult> x - y
IUnaryNegationOperators<TSelf,TResult> -x
IUnaryPlusOperators<TSelf,TResult> +x

Nota:

Algunas de las interfaces definen un operador comprobado además de un operador no comprobado normal. Los operadores comprobados se llaman en contextos comprobados y permiten que un tipo definido por el usuario defina el comportamiento de desbordamiento. Si implementa un operador comprobado, por ejemplo, CheckedSubtraction(TSelf, TOther), también debe implementar el operador desmarcado, por ejemplo, Subtraction(TSelf, TOther).

Interfaces de función

Las interfaces de función definen API matemáticas comunes que se aplican más ampliamente que a una interfaz numérica específica. Todas estas interfaces se implementan mediante IFloatingPointIeee754<TSelf>y pueden implementarse mediante otros tipos relevantes en el futuro.

Nombre de la interfaz Descripción
IExponentialFunctions<TSelf> Expone funciones exponenciales que admiten e^x, e^x - 1, 2^x, 2^x - 1, 10^xy 10^x - 1.
IHyperbolicFunctions<TSelf> Expone funciones hiperbólicas que admiten acosh(x), asinh(x), atanh(x), cosh(x), sinh(x), y tanh(x).
ILogarithmicFunctions<TSelf> Expone funciones logarítmicas que admiten ln(x), ln(x + 1), log2(x), log2(x + 1), log10(x)y log10(x + 1).
IPowerFunctions<TSelf> Expone funciones de potencia que admiten x^y.
IRootFunctions<TSelf> Expone funciones raíz compatibles con cbrt(x) y sqrt(x).
ITrigonometricFunctions<TSelf> Expone funciones trigonométricas que admiten acos(x), asin(x), atan(x)cos(x), , sin(x)y tan(x).

Interfaces de análisis y formato

El análisis y el formato son conceptos básicos de la programación. Normalmente se usan al convertir la entrada del usuario en un tipo determinado o mostrar un tipo al usuario. Estas interfaces están en el espacio de nombres System.

Nombre de la interfaz Descripción
IParsable<TSelf> Expone la compatibilidad con T.Parse(string, IFormatProvider) y T.TryParse(string, IFormatProvider, out TSelf).
ISpanParsable<TSelf> Expone la compatibilidad con T.Parse(ReadOnlySpan<char>, IFormatProvider) y T.TryParse(ReadOnlySpan<char>, IFormatProvider, out TSelf).
IFormattable 1 Expone la compatibilidad con value.ToString(string, IFormatProvider).
ISpanFormattable 1 Expone la compatibilidad con value.TryFormat(Span<char>, out int, ReadOnlySpan<char>, IFormatProvider).

1Esta interfaz no es nueva ni es genérica. Sin embargo, se implementa en todos los tipos de números y representa la operación inversa de IParsable.

Por ejemplo, el siguiente programa toma dos números como entrada y los lee desde la consola mediante un método genérico en el que el parámetro de tipo está restringido a ser IParsable<TSelf>. Calcula el promedio mediante un método genérico en el que los parámetros de tipo para los valores de entrada y resultado están restringidos a ser INumber<TSelf>y, a continuación, muestra el resultado en la consola.

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 también