Matemáticas genéricas
.NET 7 incorpora nuevas interfaces genéricas relacionadas con las matemáticas a la biblioteca de clases base. La disponibilidad de estas interfaces significa que puede restringir un parámetro de un tipo o un método genérico para que sea "similar a un número". Además, C# 11 y versiones posteriores permite definir miembros de interfaz static virtual
. Dado que los operadores deben declararse como static
, esta nueva característica de C# permite declarar operadores en las nuevas interfaces para los tipos similares a números.
Juntas, estas innovaciones permiten realizar operaciones matemáticas de forma genérica, es decir, sin tener que conocer el tipo exacto con el que se está trabajando. Por ejemplo, si desea escribir un método que sume dos números, antes 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 ser 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 de tipo T
está restringido para que sea un tipo que implemente la nueva interfaz INumber<TSelf>. INumber<TSelf> implementa la interfaz IAdditionOperators<TSelf,TOther,TResult>, que contiene el operador +. Esto permite que el método sume los dos números genéricamente. 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 serán los que más se beneficien de las interfaces de matemáticas genéricas, ya que pueden simplificar su base de código eliminando 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 tratan las interfaces de System.Numerics que describen los tipos de tipo numérico y la funcionalidad disponible para ellos.
Nombre de la interfaz | Descripción |
---|---|
IBinaryFloatingPointIeee754<TSelf> | Expone las API comunes a los tipos de punto flotante binarios1 que implementan el estándar IEEE 754. |
IBinaryInteger<TSelf> | Expone las API comunes a los 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 los tipos de números comparables (en realidad, el dominio del número "real"). |
INumberBase<TSelf> | Expone las API comunes a todos los tipos de números (en realidad, el dominio del número "complejo"). |
ISignedNumber<TSelf> | Expone las API comunes a todos los tipos de números firmados (por ejemplo, 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 . |
1 Los tipos de punto flotante binarios son Double (double
), Halfy Single (float
).
2 Los 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 se corresponde aproximadamente con un número real. Si un tipo implementa esta interfaz, significa que un valor tiene un signo (esto incluye los tipos unsigned
, 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. Se han creado otras interfaces, como IFloatingPointIeee754<TSelf>, 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 con otros tipos, como Char, DateOnly, DateTime, DateTimeOffset, Decimal, Guid, TimeOnly y TimeSpan.
En la tabla siguiente se muestran algunas de las principales API que expone 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 cero finales 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 con 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. |
|
(Otro) | (Implementa todo el conjunto de interfaces que se enumeran en Interfaces de función) | |
INumber<TSelf> | Clamp |
Restringe un valor a no más ni menos que los valores mínimo y máximo especificados. |
CopySign |
Establece el signo de un valor especificado igual que el de otro valor especificado. | |
Max |
Devuelve el mayor de dos valores y devuelve 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 y devuelve 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 los valores negativos, 0 para cero y +1 para los 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 produce OverflowException si la entrada no cabe1. | |
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 y lo ajusta si la entrada no cabe 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 true y 2.2 devuelve false . |
|
IsFinite |
Devuelve true si el valor no es infinito ni NaN . |
|
IsImaginaryNumber |
Devuelve true si el valor tiene una parte real cero. Esto significa que 0 es imaginario y 1 + 1i no lo es. |
|
IsInfinity |
Devuelve true si el valor representa el 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, como 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 y devuelve 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 y devuelve NaN si alguna de las entradas es NaN . |
|
MinMagnitudeNumber |
Devuelve el valor con un valor absoluto menor y devuelve el número si una entrada es NaN . |
|
ISignedNumber<TSelf> | NegativeOne |
Obtiene el valor -1 para el tipo. |
1 Para facilitar la comprensión del comportamiento de los tres métodos Create*
, tenga en cuenta los ejemplos siguientes.
Ejemplo de cuando se da 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 de0x0180
y el mínimo de 8 bits es0x80
, que es 128).
Ejemplo de cuando se da 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 de0xFE80
y el mínimo de 8 bits es0x80
, que es 128).
Los métodos Create*
también tienen algunas consideraciones especiales para los tipos de punto flotante IEEE 754, como float
y double
, porque tienen los valores especiales PositiveInfinity
, NegativeInfinity
y NaN
. Las tres API Create*
se comportan como CreateSaturating
. Además, mientras MinValue
y MaxValue
representan el número "normal" negativo o positivo más grande, los valores mínimo y máximo reales son NegativeInfinity
y PositiveInfinity
, por lo que dependen de 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 la división, ya que eso no es correcto para todos los tipos. Por ejemplo,
Matrix4x4 * Matrix4x4
es válido, peroMatrix4x4 / Matrix4x4
no es válido. - Normalmente, permiten que los tipos de entrada y de resultado sean diferentes para admitir escenarios como dividir dos enteros para obtener un
double
(por ejemplo,3 / 2 = 1.5
) o 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 <= y y 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 no comprobado, 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. Estas interfaces se implementan con IFloatingPointIeee754<TSelf> y puede que se implementen con 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^x y 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 de raíz que admiten 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 programación. Normalmente, se usan cuando se convierte la entrada del usuario en un tipo determinado o se muestra 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) . |
IFormattable1 | Expone la compatibilidad con value.ToString(string, IFormatProvider) . |
ISpanFormattable1 | Expone la compatibilidad con value.TryFormat(Span<char>, out int, ReadOnlySpan<char>, IFormatProvider) . |
1 Esta interfaz no es nueva ni es genérica. Sin embargo, la implementan todos los tipos numéricos y representa la operación inversa de IParsable
.
Por ejemplo, el siguiente programa toma dos números como entrada, leyéndolos de la consola con un método genérico donde el parámetro de tipo está restringido a ser IParsable<TSelf>. Calcula el promedio con un método genérico donde los parámetros de tipo de 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
*/