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


Общая математика

.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, которые описывают типы, похожие на числа, и функции, доступные для них.

Имя интерфейса Описание
IBinaryFloatingPointIeee754<TSelf> Обеспечивает общие API для типов с плавающей точкой, реализующих стандарт IEEE 7541.
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Двоичные целочисленные типы: Byte (byte), Int16 (short), Int32 (int), Int64 (long), Int128, IntPtr (nint), SByte (sbyte), UInt16 (ushort), UInt32 (uint), UInt64 (ulong), UInt128, и UIntPtr (nuint).

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

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

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

Интерфейс Имя API Описание
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.MinValue или T.MaxValue если входные данные не подходят.1
CreateTruncating Создает значение из другого значения, обтекая вокруг, если входные данные не могут соответствовать. 1
IsComplexNumber Возвращает значение true, если значение имеет ненульную реальную часть и ненульную мнимую часть.
IsEvenInteger Возвращает значение true, если значение является даже целым числом. 2.0 возвращает true, а 2.2 возвращает false.
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 и , так как они имеют специальные значения double, PositiveInfinityи NegativeInfinityNaN. Все три Create* API ведут себя как CreateSaturating. Кроме того, в то время как MinValue и MaxValue представляют наибольшее отрицательное и положительное "нормальное" число, фактические минимальные и максимальные значения — это NegativeInfinity и PositiveInfinity, поэтому они зажимаются до этих значений вместо этого.

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

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

  • Они явно не объединяют такие операции, как умножение и деление, так как это не правильно для всех типов. Например, Matrix4x4 * Matrix4x4 является допустимым, но Matrix4x4 / Matrix4x4 недопустимым.
  • Обычно они позволяют типам входных и результирующих данных отличаться для сценариев поддержки, таких как деление двух целых чисел для получения double, например, 3 / 2 = 1.5или вычисления среднего значения набора целых чисел.
Имя интерфейса Определенные операторы
IAdditionOperators<TSelf,TOther,TResult> x + y
IBitwiseOperators<TSelf,TOther,TResult> x & y, 'x | y', x ^ yи ~x
IComparisonOperators<TSelf,TOther,TResult> x < y, x > y, x <= y и x >= y
IDecrementOperators<TSelf> --x и x--.
IDivisionOperators<TSelf,TOther,TResult> x / y
IEqualityOperators<TSelf,TOther,TResult> x == y и x != y.
IIncrementOperators<TSelf> ++x и x++.
IModulusOperators<TSelf,TOther,TResult> x % y
IMultiplyOperators<TSelf,TOther,TResult> x * y
IShiftOperators<TSelf,TOther,TResult> x << y и x >> y.
ISubtractionOperators<TSelf,TOther,TResult> x - y
IUnaryNegationOperators<TSelf,TResult> -x
IUnaryPlusOperators<TSelf,TResult> +x

Замечание

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

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

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

Имя интерфейса Описание
IExponentialFunctions<TSelf> Предоставляет экспоненциальные функции, поддерживающие e^x, e^x - 1, 2^x, 2^x - 1, 10^x и 10^x - 1.
IHyperbolicFunctions<TSelf> Предоставляет гиперболические функции, поддерживающие acosh(x), asinh(x), atanh(x), cosh(x), sinh(x) и tanh(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 пространстве имен.

Имя интерфейса Описание
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).
IFormattable 1 Предоставляет поддержку value.ToString(string, IFormatProvider).
ISpanFormattable 1 Предоставляет поддержку 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
*/

См. также