Прочитать на английском

Поделиться через


Арифметические операторы в универсальных типах

.NET 7 представляет новые универсальные интерфейсы, связанные с математикой, в библиотеке базовых классов. Доступность этих интерфейсов означает, что параметр типа универсального типа или метода может быть "числовым". Кроме того, C# 11 и более поздних версий позволяет определять static virtual элементы интерфейса. Так как операторы должны быть объявлены как static, эта новая функция C# позволяет объявлять операторы в новых интерфейсах для типов, таких как число.

Вместе эти инновации позволяют выполнять математические операции универсально, т. е. не зная точного типа, с которым вы работаете. Например, если вы хотите написать метод, добавляющий два числа, ранее пришлось добавить перегрузку метода для каждого типа (например, static int Add(int first, int second) и static float Add(float first, float second)). Теперь можно написать один универсальный метод, где параметр типа ограничен числом типа. Например:

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

В этом методе параметр T типа ограничен типом, реализующим новый INumber<TSelf> интерфейс. INumber<TSelf>IAdditionOperators<TSelf,TOther,TResult> реализует интерфейс, содержащий оператор +. Это позволяет методу универсально добавлять два числа. Этот метод можно использовать с любым из. Встроенные числовые типы NET, так как они были обновлены для реализации INumber<TSelf> в .NET 7.

Авторы библиотеки получают большую выгоду от универсальных математических интерфейсов, так как они могут упростить базу кода, удаляя избыточные перегрузки. Другие разработчики будут использовать косвенно, так как api, которые они используют, могут начать поддерживать больше типов.

Интерфейсы

Интерфейсы были разработаны достаточно точно, чтобы пользователи могли определять собственные интерфейсы поверх, а также быть достаточно детализированными, чтобы они легко потребляли. В этой степени существует несколько основных числовых интерфейсов, с которыми большинство пользователей будут взаимодействовать, например INumber<TSelf> и IBinaryInteger<TSelf>. Более подробные интерфейсы, такие как IAdditionOperators<TSelf,TOther,TResult> и ITrigonometricFunctions<TSelf>, поддерживают эти типы и доступны разработчикам, которые определяют собственные числовые интерфейсы, относящиеся к домену.

Числовые интерфейсы

В этом разделе описываются интерфейсы, описывающие System.Numerics типы числа и доступные для них функциональные возможности.

Имя интерфейса Description
IBinaryFloatingPointIeee754<TSelf> Предоставляет ИНТЕРФЕЙСы API, общие для типовс плавающей запятой 1, реализующих стандарт IEEE 754.
IBinaryInteger<TSelf> Предоставляет ИНТЕРФЕЙСы API, общие для двоичных целыхчисел 2.
IBinaryNumber<TSelf> Предоставляет API-интерфейсы, общие для двоичных чисел.
IFloatingPoint<TSelf> Предоставляет ИНТЕРФЕЙСы API, общие для типов с плавающей запятой.
IFloatingPointIeee754<TSelf> Предоставляет ИНТЕРФЕЙСы API, общие для типов с плавающей запятой, реализующих стандарт IEEE 754.
INumber<TSelf> Предоставляет ИНТЕРФЕЙСы API, общие для сопоставимых типов чисел (фактически "реальный" домен чисел).
INumberBase<TSelf> Предоставляет ИНТЕРФЕЙСы API, общие для всех типов чисел (фактически "сложный" домен номеров).
ISignedNumber<TSelf> Предоставляет ИНТЕРФЕЙСы API, общие для всех подписанных типов чисел (например, концепции NegativeOne).
IUnsignedNumber<TSelf> Предоставляет ИНТЕРФЕЙСы API, общие для всех типов незначенных чисел.
IAdditiveIdentity<TSelf,TResult> Предоставляет концепцию (x + T.AdditiveIdentity) == x.
IMinMaxValue<TSelf> Предоставляет концепцию T.MinValue и T.MaxValue.
IMultiplicativeIdentity<TSelf,TResult> Предоставляет концепцию (x * T.MultiplicativeIdentity) == x.

1 Двоичные типы с плавающей запятой (doubleDouble), Halfи Single (float).

2. Типы целых чиселBytebyte(), (), Int16 (short), Int32 (int), Int64IntPtrnintSByteInt128UInt16ushortsbyteuintlongUInt32 (ulong) UInt128UInt64 и UIntPtr ().nuint

Интерфейс, который вы, скорее всего, используется напрямуюINumber<TSelf>, который примерно соответствует реальному числу. Если тип реализует этот интерфейс, это означает, что значение имеет знак ( unsigned включает типы, которые считаются положительными) и можно сравнить с другими значениями того же типа. INumberBase<TSelf> предоставляет более сложные понятия, такие как сложные и мнимые числа, например квадратный корень отрицательного числа. Другие интерфейсы, например, были созданы, так как IFloatingPointIeee754<TSelf>не все операции имеет смысл для всех типов чисел, например вычисление пола числа имеет смысл только для типов с плавающей запятой. В библиотеке базовых классов .NET тип Double с плавающей запятой реализует IFloatingPointIeee754<TSelf> , но Int32 не выполняет.

Некоторые интерфейсы также реализуются различными другими типами, включая Char, , DateOnly, DateTime, DateTimeOffset, GuidDecimalTimeOnlyи .TimeSpan

В следующей таблице показаны некоторые основные API, предоставляемые каждым интерфейсом.

Интерфейс Имя API Description
IBinaryInteger<TSelf> DivRem Вычисляет цитент и оставшуюся часть одновременно.
LeadingZeroCount Подсчитывает число начальных нулевых битов в двоичном представлении.
PopCount Подсчитывает количество битов набора в двоичном представлении.
RotateLeft Поворот битов влево, иногда также называется циклическим сдвигом влево.
RotateRight Поворот битов вправо, иногда также называется циклическим сдвигом вправо.
TrailingZeroCount Подсчитывает число конечных нулей в двоичном представлении.
IFloatingPoint<TSelf> Ceiling Округляет значение в сторону положительной бесконечности. +4,5 становится +5, а -4,5 становится -4.
Floor Округляет значение в сторону отрицательной бесконечности. +4,5 становится +4, а -4,5 становится -5.
Round Округляет значение с помощью указанного режима округления.
Truncate Округляет значение к нулю. +4,5 становится +4, а -4,5 становится -4.
IFloatingPointIeee754<TSelf> E Возвращает значение, представляющее число Эйлера для типа.
Epsilon Возвращает наименьшее представляющее значение, которое больше нуля для типа.
NaN Возвращает значение, представляющее NaN тип.
NegativeInfinity Возвращает значение, представляющее -Infinity тип.
NegativeZero Возвращает значение, представляющее -Zero тип.
Pi Возвращает значение, представляющее Pi тип.
PositiveInfinity Возвращает значение, представляющее +Infinity тип.
Tau Возвращает значение, представляющее Tau (2 * Pi) для типа.
(Другое) (Реализует полный набор интерфейсов, перечисленных в разделе Интерфейсы функций.)
INumber<TSelf> Clamp Ограничивает значение не более и не меньше указанного минимального и максимального значения.
CopySign Задает знак указанного значения таким же, как и другое указанное значение.
Max Возвращает больше двух значений, возвращая NaN при наличии входных данных NaN.
MaxNumber Возвращает больше двух значений, возвращая число, если один входной NaN.
Min Возвращает меньшее из двух значений, возвращая NaN при наличии входных данных NaN.
MinNumber Возвращает меньшее из двух значений, возвращая число, если одно входное значение NaN.
Sign Возвращает значение -1 для отрицательных значений, от 0 до нуля и +1 для положительных значений.
INumberBase<TSelf> One Возвращает значение 1 для типа.
Radix Возвращает радикс или базу для типа. Int32 возвращает 2. Decimal возвращает 10.
Zero Возвращает значение 0 для типа.
CreateChecked Создает значение, вызывая OverflowException исключение, если входные данные не могут соответствовать.1
CreateSaturating Создает значение, зажимая или T.MinValueT.MaxValue если входные данные не могут соответствовать.1
CreateTruncating Создает значение из другого значения, обтекая вокруг, если входные данные не могут соответствовать.1
IsComplexNumber Возвращает значение true, если значение имеет ненульную реальную часть и ненульную мнимую часть.
IsEvenInteger Возвращает значение true, если значение является даже целым числом. 2.0 возвращает trueи возвращает false2.2.
IsFinite Возвращает значение true, если значение не бесконечно, а не NaN.
IsImaginaryNumber Возвращает значение true, если значение имеет нулю реальную часть. Это означает 0 , что это мнимый и 1 + 1i не является.
IsInfinity Возвращает значение true, если значение представляет бесконечность.
IsInteger Возвращает значение true, если значение является целым числом. Возврат 2.0 и 3.0 true, а также возврат 2.2 и 3.1 false.
IsNaN Возвращает значение true, если значение представляет NaN.
IsNegative Возвращает значение true, если значение отрицательное. Это включает в себя -0.0.
IsPositive Возвращает значение true, если значение является положительным. Это включает 0 и +0,0.
IsRealNumber Возвращает значение true, если значение имеет нулевая мнимая часть. Это означает, что 0 является реальным, как и все INumber<T> типы.
IsZero Возвращает значение true, если значение представляет ноль. Это включает 0, +0.0 и -0.0.
MaxMagnitude Возвращает значение с большим абсолютным значением, возвращая NaN , если входные данные имеют значение NaN.
MaxMagnitudeNumber Возвращает значение с большим абсолютным значением, возвращая число, если один входной.NaN
MinMagnitude Возвращает значение с меньшим абсолютным значением, возвращая NaN , если входные данные имеют значение NaN.
MinMagnitudeNumber Возвращает значение с меньшим абсолютным значением, возвращая число, если одно входное значение NaN.
ISignedNumber<TSelf> NegativeOne Возвращает значение -1 для типа.

1. Чтобы понять поведение трех Create* методов, рассмотрим следующие примеры.

Пример, если задано слишком большое значение:

  • byte.CreateChecked(384) вызовет OverflowExceptionисключение .
  • byte.CreateSaturating(384) возвращает значение 255, так как 384 больше Byte.MaxValue (что составляет 255).
  • byte.CreateTruncating(384) возвращает 128, так как он принимает наименьшее 8 бит (384 имеет шестнадцатеричное представление 0x0180, а наименьшее 8 битов — 0x80128).

Пример, если задано слишком небольшое значение:

  • byte.CreateChecked(-384) вызовет OverflowExceptionисключение .
  • byte.CreateSaturating(-384) возвращает значение 0, так как -384 меньше Byte.MinValue (что равно 0).
  • byte.CreateTruncating(-384) возвращает 128, так как он принимает наименьшее 8 бит (384 имеет шестнадцатеричное представление 0xFE80, а наименьшее 8 битов — 0x80128).

Методы Create* также имеют некоторые особые аспекты для типов с плавающей запятой IEEE 754, например float и , так как они имеют специальные значения PositiveInfinity, NegativeInfinityи doubleNaN. Все три Create* API ведут себя как CreateSaturating. Кроме того, в то время как MinValue и MaxValue представляет наибольшее отрицательное или положительное "нормальное" число, фактические и максимальные значения, NegativeInfinityPositiveInfinityпоэтому они зажимаются с этими значениями.

Интерфейсы операторов

Интерфейсы операторов соответствуют различным операторам, доступным для языка C#.

  • Они явно не объединяют такие операции, как умножение и деление, так как это не правильно для всех типов. Например, допустимо, Matrix4x4 * Matrix4x4 но Matrix4x4 / Matrix4x4 не является допустимым.
  • Обычно они позволяют типам входных и результирующих данных отличаться для сценариев поддержки, таких как деление двух целых чисел для получения double, например, 3 / 2 = 1.5или вычисления среднего значения набора целых чисел.

Примечание

Некоторые интерфейсы определяют оператор проверка в дополнение к обычному оператору un проверка ed. Проверенные операторы вызываются в проверка контекстах и позволяют определяемого пользователем типа определять поведение переполнения. Если вы реализуете оператор проверка, например, CheckedSubtraction(TSelf, TOther)необходимо также реализовать оператор un проверка ed, напримерSubtraction(TSelf, TOther).

Интерфейсы функций

Интерфейсы функций определяют общие математические API, которые применяются более широко, чем к определенному числового интерфейса. Эти интерфейсы реализуются всеми и могут быть реализованы IFloatingPointIeee754<TSelf>другими соответствующими типами в будущем.

Имя интерфейса Description
IExponentialFunctions<TSelf> Предоставляет экспоненциальные функции, поддерживающие e^x, e^x - 1, , 2^xи2^x - 110^x.10^x - 1
IHyperbolicFunctions<TSelf> Предоставляет гиперболические функции, поддерживающие acosh(x), , , atanh(x)иcosh(x)sinh(x).tanh(x)asinh(x)
ILogarithmicFunctions<TSelf> Предоставляет логарифмические функции, поддерживающие ln(x), ln(x + 1), , log2(x)иlog2(x + 1)log10(x).log10(x + 1)
IPowerFunctions<TSelf> Предоставляет вспомогательные x^yфункции питания.
IRootFunctions<TSelf> Предоставляет вспомогательные cbrt(x)sqrt(x)и вспомогательные функции корневых функций.
ITrigonometricFunctions<TSelf> Предоставляет функции тригонометрики, поддерживающие acos(x), asin(x), , atan(x), cos(x)sin(x)и tan(x).

Анализ и форматирование интерфейсов

Анализ и форматирование являются основными понятиями программирования. Они часто используются при преобразовании входных данных пользователя в заданный тип или отображении типа для пользователя. Эти интерфейсы находятся в System пространстве имен.

Имя интерфейса Description
IParsable<TSelf> Предоставляет поддержку T.Parse(string, IFormatProvider) и T.TryParse(string, IFormatProvider, out TSelf).
ISpanParsable<TSelf> Предоставляет поддержку T.Parse(ReadOnlySpan<char>, IFormatProvider) и T.TryParse(ReadOnlySpan<char>, IFormatProvider, out TSelf).
IFormattable1 Предоставляет поддержку value.ToString(string, IFormatProvider).
ISpanFormattable1 Предоставляет поддержку value.TryFormat(Span<char>, out int, ReadOnlySpan<char>, IFormatProvider).

1Этот интерфейс не является новым, и не является универсальным. Однако он реализуется всеми типами чисел и представляет обратную операцию IParsable.

Например, следующая программа принимает два числа в качестве входных данных, считывая их из консоли с помощью универсального метода, в котором параметр типа ограничен.IParsable<TSelf> Он вычисляет среднее значение с помощью универсального метода, в котором параметры типа для входных и результирующих значений ограничены INumber<TSelf>, а затем отображаются результаты в консоли.

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
*/

См. также