영어로 읽기

다음을 통해 공유


제네릭 수학

.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의 기본 제공 숫자 형식이 모두 .NET 7에서 INumber<TSelf>를 구현하도록 업데이트되었기 때문에 사용할 수 있습니다.

라이브러리 작성자는 "중복" 오버로드를 제거하여 코드 기반을 간소화할 수 있으므로 제네릭 수학 인터페이스의 이점을 가장 많이 누릴 수 있습니다. 다른 개발자들은 그들이 사용하는 API가 더 많은 형식을 지원하기 시작할 수 있기 때문에 간접적으로 이익을 얻을 것입니다.

인터페이스

인터페이스는 사용자가 자신의 인터페이스를 정의할 수 있을 만큼 세분화된 동시에 사용하기 쉽도록 세분화되도록 설계되었습니다. 그 정도에는 INumber<TSelf>IBinaryInteger<TSelf>와 같이 대부분의 사용자가 상호 작용할 몇 가지 핵심 숫자 인터페이스가 있습니다. IAdditionOperators<TSelf,TOther,TResult>ITrigonometricFunctions<TSelf>와 같은 보다 세분화된 인터페이스는 이러한 형식을 지원하며 자체 도메인별 숫자 인터페이스를 정의하는 개발자가 사용할 수 있습니다.

숫자 인터페이스

이 섹션에서는 숫자형 형식과 해당 형식에 사용 가능한 기능을 설명하는 System.Numerics의 인터페이스에 대해 설명합니다.

인터페이스 이름 설명
IBinaryFloatingPointIeee754<TSelf> IEEE 754 표준을 구현하는 이진 파일 부동 소수점 형식1에 공통적인 API를 노출합니다.
IBinaryInteger<TSelf> 이진 정수2에 공통적인 API를 노출합니다.
IBinaryNumber<TSelf> 이진수에 공통적인 API를 노출합니다.
IFloatingPoint<TSelf> 부동 소수점 형식에 공통적인 API를 노출합니다.
IFloatingPointIeee754<TSelf> IEEE 754 표준을 구현하는 부동 소수점 형식에 공통적인 API를 노출합니다.
INumber<TSelf> 비교 가능한 숫자 형식(효과적으로 "실수" 숫자 도메인)에 공통적인 API를 노출합니다.
INumberBase<TSelf> 모든 숫자 형식(사실상 "복소수" 도메인)에 공통적인 API를 노출합니다.
ISignedNumber<TSelf> 모든 부호 있는 숫자 형식(예: NegativeOne의 개념)에 공통적인 API를 노출합니다.
IUnsignedNumber<TSelf> 모든 부호 없는 숫자 형식에 공통적인 API를 노출합니다.
IAdditiveIdentity<TSelf,TResult> (x + T.AdditiveIdentity) == x의 개념을 노출합니다.
IMinMaxValue<TSelf> T.MinValueT.MaxValue의 개념을 노출합니다.
IMultiplicativeIdentity<TSelf,TResult> (x * T.MultiplicativeIdentity) == x의 개념을 노출합니다.

1이진 파일 부동 소수점 형식Double(double), HalfSingle(float)입니다.

2이진 정수 형식Byte(byte), Int16(short), Int32(int), Int64(long), Int128, IntPtr(nint), SByte(sbyte), UInt16(ushort), UInt32(uint), UInt64(ulong), UInt128UIntPtr(nuint)입니다.

직접적으로 사용할 가능성이 가장 높은 인터페이스는 INumber<TSelf>이며 이는 대략 실수 숫자에 해당합니다. 형식이 이 인터페이스를 구현하는 경우 이는 값에 부호(양수로 간주되는 unsigned 형식 포함)가 있으며 동일한 형식의 다른 값과 비교할 수 있음을 의미합니다. INumberBase<TSelf>복소수허수 숫자(예: 음수의 제곱근)와 같은 고급 개념을 제공합니다. IFloatingPointIeee754<TSelf>와 같은 다른 인터페이스는 모든 연산이 모든 숫자 형식에 대해 의미가 있는 것은 아니기 때문에 만들어졌습니다. 예를 들어, 숫자의 하한 계산은 부동 소수점 형식에만 의미가 있습니다. .NET 기본 클래스 라이브러리에서 부동 소수점 형식 DoubleIFloatingPointIeee754<TSelf>를 구현하지만 Int32는 구현하지 않습니다.

여러 인터페이스는 Char, DateOnly, DateTime, DateTimeOffset, Decimal, Guid, TimeOnlyTimeSpan을 포함한 다양한 다른 형식으로도 구현됩니다.

다음 표에서는 각 인터페이스에서 제공하는 핵심 API 중 일부를 보여 줍니다.

인터페이스 API 이름 설명
IBinaryInteger<TSelf> DivRem 몫과 나머지를 동시에 계산합니다.
LeadingZeroCount 이진 표현에서 선행 0 비트 수를 셉니다.
PopCount 이진 표현에서 설정된 비트 수를 셉니다.
RotateLeft 비트를 왼쪽으로 회전합니다. 순환 왼쪽 시프트라고도 합니다.
RotateRight 비트를 오른쪽으로 회전합니다. 순환 오른쪽 시프트라고도 합니다.
TrailingZeroCount 이진 표현에서 후행 0비트의 수를 셉니다.
IFloatingPoint<TSelf> Ceiling 값을 양의 무한대로 반올림합니다. +4.5는 +5가 되고, -4.5는 -4가 됩니다.
Floor 값을 음의 무한대로 반올림합니다. +4.5는 +4가 되고, -4.5는 -5가 됩니다.
Round 지정된 반올림 모드를 사용하여 값을 반올림합니다.
Truncate 값을 0에 가깝게 반올림합니다. +4.5는 +4가 되고, -4.5는 -4가 됩니다.
IFloatingPointIeee754<TSelf> E 형식에 대한 오일러 수를 나타내는 값을 가져옵니다.
Epsilon 해당 형식에 대해 0보다 큰 표현 가능한 가장 작은 값을 가져옵니다.
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이면 0, 양수 값이면 +1을 반환합니다.
INumberBase<TSelf> One 형식에 대해 값 1을 가져옵니다.
Radix 형식에 대한 기수 또는 베이스를 가져옵니다. Int32가 2를 반환합니다. Decimal은 10을 반환합니다.
Zero 형식에 대해 값 0을 가져옵니다.
CreateChecked 입력이 맞지 않으면 OverflowException을 throw하여 값을 만듭니다.1
CreateSaturating 입력이 맞지 않으면 T.MinValue 또는 T.MaxValue로 고정하여 값을 만듭니다.1
CreateTruncating 다른 값에서 값을 만들고 입력이 맞지 않으면 래핑합니다.1
IsComplexNumber 값에 0이 아닌 실수 부분과 0이 아닌 허수 부분이 있는 경우 true를 반환합니다.
IsEvenInteger 값이 짝수이면 true를 반환합니다. 2.0은 true를 반환하고 2.2는 false를 반환합니다.
IsFinite 값이 무한하지 않고 NaN이 아닌 경우 true를 반환합니다.
IsImaginaryNumber 값에 실수부가 0인 경우 true를 반환합니다. 이는 0이 허수이고 1 + 1i가 그렇지 않음을 의미합니다.
IsInfinity 값이 무한대를 나타내는 경우 true를 반환합니다.
IsInteger 값이 정수이면 true를 반환합니다. 2.0과 3.0은 true를 반환하고, 2.2와 3.1은 false를 반환합니다.
IsNaN 값이 NaN을 나타내는 경우 true를 반환합니다.
IsNegative 값이 음수이면 true를 반환합니다. 여기에는 -0.0이 포함됩니다.
IsPositive 값이 양수이면 true를 반환합니다. 여기에는 0과 +0.0이 포함됩니다.
IsRealNumber 값에 허수부가 0인 경우 true를 반환합니다. 이는 모든 INumber<T> 형식과 마찬가지로 0이 실수임을 의미합니다.
IsZero 값이 0을 나타내는 경우 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을 throw합니다.
  • 384가 Byte.MaxValue(255)보다 크므로 byte.CreateSaturating(384)는 255를 반환합니다.
  • byte.CreateTruncating(384)는 가장 낮은 8비트를 사용하므로 128을 반환합니다(384는 0x0180의 16진수 표현을 가지며 가장 낮은 8비트는 0x80, 즉 128입니다).

너무 작은 값이 지정된 경우의 예:

  • byte.CreateChecked(-384)OverflowException을 throw합니다.
  • -384가 Byte.MinValue(0)보다 작기 때문에 byte.CreateSaturating(-384)는 0을 반환합니다.
  • byte.CreateTruncating(-384)는 가장 낮은 8비트를 사용하므로 128을 반환합니다(384는 0xFE80의 16진수 표현을 가지며 가장 낮은 8비트는 0x80, 즉 128입니다).

Create* 메서드에는 특수 값 PositiveInfinity, NegativeInfinityNaN이 있으므로 floatdouble과 같은 IEEE 754 부동 소수점 형식에 대한 몇 가지 특별한 고려 사항도 있습니다. 세 가지 Create* API는 모두 CreateSaturating처럼 작동합니다. 또한 MinValueMaxValue는 가장 큰 음수/양수 "일반" 숫자를 나타내지만 실제 최솟값과 최댓값은 NegativeInfinityPositiveInfinity이므로 대신 이러한 값으로 고정됩니다.

운영자 인터페이스

연산자 인터페이스는 C# 언어에서 사용할 수 있는 다양한 연산자에 해당합니다.

  • 곱셈과 나눗셈과 같은 연산은 모든 형식에 대해 올바르지 않기 때문에 명시적으로 쌍을 이루지 않습니다. 예를 들어, Matrix4x4 * Matrix4x4는 유효하지만 Matrix4x4 / Matrix4x4는 유효하지 않습니다.
  • 일반적으로 두 개의 정수를 나누어 double(예: 3 / 2 = 1.5)을 가져오거나 정수 집합의 평균을 계산하는 등의 시나리오를 지원하기 위해 입력 및 결과 형식을 다르게 할 수 있습니다.

참고

일부 인터페이스는 확인되지 않은 일반 연산자 외에 확인된 연산자를 정의합니다. 확인된 연산자는 확인된 컨텍스트에서 호출되며 사용자 정의 형식이 오버플로 동작을 정의하도록 허용합니다. 확인된 연산자(예: CheckedSubtraction(TSelf, TOther))를 구현하는 경우 확인되지 않은 연산자(예: Subtraction(TSelf, TOther))도 구현해야 합니다.

함수 인터페이스

함수 인터페이스는 특정 숫자 인터페이스보다 더 광범위하게 적용되는 일반적인 수학 API를 정의합니다. 이러한 인터페이스는 모두 IFloatingPointIeee754<TSelf>에 의해 구현되며 향후 다른 관련 형식에 의해 구현될 수도 있습니다.

인터페이스 이름 설명
IExponentialFunctions<TSelf> e^x, e^x - 1, 2^x, 2^x - 1, 10^x10^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)에 대한 지원을 노출합니다.
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
*/

참고 항목