次の方法で共有


ジェネリック型数値演算

.NET 7 では、基底クラス ライブラリに新しい数学関連のジェネリック インターフェイスが導入されています。 これらのインターフェイスの可用性は、ジェネリック型またはメソッドの型パラメーターを "数値に似た" ように制限できることを意味します。 さらに、C# 11 以降ではstatic virtualインターフェイス メンバーを定義できます。 演算子は staticとして宣言する必要があるため、この新しい C# 機能を使用すると、数値に似た型の新しいインターフェイスで演算子を宣言できます。

これらのイノベーションを組み合わせることで、数学的演算を汎用的に実行できます。つまり、使用している正確な型を知る必要はありません。 たとえば、2 つの数値を追加するメソッドを記述する場合は、以前は各型 (たとえば、 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>インターフェイスを実装します。 これにより、メソッドは 2 つの数値を一般的に追加できます。 このメソッドは、次のいずれかと共に使用できます。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)、 Half、および Single (float) です。

2バイナリ 整数型 は、 Byte (byte)、 Int16 (short)、 Int32 (int)、 Int64 (long)、 Int128IntPtr (nint)、 SByte (sbyte)、 UInt16 (ushort)、 UInt32 (uint)、 UInt64 (ulong)、 UInt128、および UIntPtr (nuint)。

直接使用する可能性が最も高いインターフェイスは INumber<TSelf>であり、これはほぼ 数に対応しています。 型がこのインターフェイスを実装する場合は、値に符号 (正と見なされる unsigned 型が含まれます) があり、同じ型の他の値と比較できることを意味します。 INumberBase<TSelf> は、 複素 数や 虚数 など、より高度な概念 (負の数の平方根など) を与えます。 IFloatingPointIeee754<TSelf>などの他のインターフェイスは、すべての操作がすべての数値型に対して意味を持つわけではないため作成されました。たとえば、数値の階数の計算は浮動小数点型に対してのみ有効です。 .NET 基本クラス ライブラリでは、浮動小数点型 DoubleIFloatingPointIeee754<TSelf> を実装しますが、 Int32 は実装しません。

インターフェイスの一部は、 CharDateOnlyDateTimeDateTimeOffsetDecimalGuidTimeOnlyTimeSpanなど、さまざまな種類によっても実装されます。

次の表は、各インターフェイスによって公開されるコア 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 型の Euler の数値を表す値を取得します。
Epsilon 型の 0 より大きい最小の表現可能な値を取得します。
NaN 型の NaN を表す値を取得します。
NegativeInfinity 型の -Infinity を表す値を取得します。
NegativeZero 型の -Zero を表す値を取得します。
Pi 型の Pi を表す値を取得します。
PositiveInfinity 型の +Infinity を表す値を取得します。
Tau 型の Tau (2 * Pi) を表す値を取得します。
(その他) (関数インターフェイスの下に一覧表示されているインターフェイスの完全なセット を実装します)。
INumber<TSelf> Clamp 指定した最小値と最大値以下に値を制限します。
CopySign 指定した値の符号を別の指定した値と同じに設定します。
Max 2 つの値のうち大きい方を返し、いずれかの入力がNaN場合はNaNを返します。
MaxNumber 2 つの値の大きい方を返し、1 つの入力が NaN場合は数値を返します。
Min 2 つの値のうち小さい方を返し、いずれかの入力がNaN場合はNaNを返します。
MinNumber 2 つの値のうち小さい方を返し、1 つの入力が NaN場合は数値を返します。
Sign 負の値の -1、0 の場合は 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 値に 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 より大きい絶対値を持つ値を返し、1 つの入力が NaN場合は数値を返します。
MinMagnitude 絶対値が小さい値を返し、いずれかの入力がNaN場合はNaNを返します。
MinMagnitudeNumber 絶対値が小さい値を返し、1 つの入力が NaN場合は数値を返します。
ISignedNumber<TSelf> NegativeOne 型の -1 値を取得します。

13 つの Create* メソッドの動作を理解するには、次の例を検討してください。

大きすぎる値を指定した場合の例:

  • byte.CreateChecked(384)OverflowExceptionをスローします。
  • byte.CreateSaturating(384) 384 が Byte.MaxValue (255) より大きいため、255 が返されます。
  • byte.CreateTruncating(384) は、最下位 8 ビットを受け取るため 128 を返します (384 は 0x0180 の 16 進表現を持ち、最下位 8 ビットは 0x80(128) です)。

小さすぎる値を指定した場合の例:

  • byte.CreateChecked(-384)OverflowExceptionをスローします。
  • byte.CreateSaturating(-384) -384 が Byte.MinValue (0) よりも小さいため、0 が返されます。
  • byte.CreateTruncating(-384) は、最下位 8 ビットを受け取るため 128 を返します (384 は 0xFE80 の 16 進表現を持ち、最下位 8 ビットは 0x80(128) です)。

Create*メソッドには、floatdoubleなど、IEEE 754 浮動小数点型には特別な値PositiveInfinityNegativeInfinity、およびNaNがあるため、いくつかの特別な考慮事項があります。 3 つの Create* API はすべて、 CreateSaturatingとして動作します。 また、 MinValueMaxValue は最大の負/正の "正規" 数を表しますが、実際の最小値と最大値は NegativeInfinity され、 PositiveInfinityされるため、代わりにこれらの値にクランプされます。

演算子インターフェイス

演算子インターフェイスは、C# 言語で使用できるさまざまな演算子に対応します。

  • 乗算や除算などの演算は、すべての型に対して正しいとは限らないため、明示的にはペアになりません。 たとえば、 Matrix4x4 * Matrix4x4 は有効ですが、 Matrix4x4 / Matrix4x4 は有効ではありません。
  • 通常、入力と結果の種類は、2 つの整数を除算して 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 < yx > yx <= y、および x >= y
IDecrementOperators<TSelf> --xx--
IDivisionOperators<TSelf,TOther,TResult> x / y
IEqualityOperators<TSelf,TOther,TResult> x == yx != y
IIncrementOperators<TSelf> ++xx++
IModulusOperators<TSelf,TOther,TResult> x % y
IMultiplyOperators<TSelf,TOther,TResult> x * y
IShiftOperators<TSelf,TOther,TResult> x << yx >> y
ISubtractionOperators<TSelf,TOther,TResult> x - y
IUnaryNegationOperators<TSelf,TResult> -x
IUnaryPlusOperators<TSelf,TResult> +x

一部のインターフェイスでは、通常の unchecked 演算子に加えて、checked 演算子が定義されています。 チェックされた演算子は、チェックされたコンテキストで呼び出され、ユーザー定義型でオーバーフロー動作を定義できます。 たとえば、 CheckedSubtraction(TSelf, TOther)など、checked 演算子を実装する場合は、 Subtraction(TSelf, TOther)などのチェックされていない演算子も実装する必要があります。

関数インターフェイス

関数インターフェイスは、特定の 数値インターフェイスよりも広く適用される一般的な数学 API を定義します。 これらのインターフェイスはすべて IFloatingPointIeee754<TSelf>によって実装され、将来、他の関連する型によって実装される可能性があります。

インターフェイス名 説明
IExponentialFunctions<TSelf> e^xe^x - 12^x2^x - 110^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の逆演算を表します。

たとえば、次のプログラムは入力として 2 つの数値を受け取り、型パラメーターが 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
*/

こちらも参照ください