英語で読む

次の方法で共有


ジェネリック型数値演算

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

これらのイノベーションを組み合わせることで、数学演算を汎用的に実行できます。つまり、使用している正確な型を知る必要がありません。 たとえば、2 つの数値を加算するメソッドを記述する場合、以前は各型 (たとえば、static int Add(int first, int second)static float Add(float first, float second)) に対してメソッドのオーバーロードを追加する必要がありました。 今回、型パラメーターが数値に似た型に制限される単一のジェネリック メソッドを記述できるようになりました。 次に例を示します。

C#
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 バイナリ表記における先行ゼロのビット数をカウントします。
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 2 つの値のうち大きい方を返します (どちらかの入力が NaN である場合は NaN が返されます)。
MaxNumber 2 つの値のうち大きい方を返します (どちらかの入力が NaN である場合はその数値が返されます)。
Min 2 つの値のうち小さい方を返します (どちらかの入力が NaN である場合は NaN が返されます)。
MinNumber 2 つの値のうち小さい方を返します (どちらかの入力が 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 値が無限ではなく、NaN でない場合に true を返します。
IsImaginaryNumber 値にゼロの実部がある場合に 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 値にゼロの虚部がある場合に true を返します。 これは、すべての INumber<T> 型と同様に、0 が実数であることを意味します。
IsZero 値がゼロを表す場合に true を返します。 これには、0、+0.0、-0.0 が含まれます。
MaxMagnitude 絶対値が大きい方の値を返し、どちらかの入力が NaN である場合は、NaN を返します。
MaxMagnitudeNumber 絶対値が大きい方の値を返し、どちらかの入力が NaN である場合は、その数値が返されます。
MinMagnitude 絶対値が小さい方の値を返し、どちらかの入力が NaN である場合は、NaN を返します。
MinMagnitudeNumber 絶対値が小さい方の値を返し、どちらかの入力が NaN である場合は、その数値が返されます。
ISignedNumber<TSelf> NegativeOne 型の値 -1 を取得します。

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

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

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

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

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

Create* メソッドには、floatdouble などの、IEEE 754 浮動小数点型に関する特別な考慮事項もあります。それらには PositiveInfinityNegativeInfinityNaN の特殊な値が含まれるためです。 3 つの Create* API はすべて CreateSaturating のように動作します。 また、MinValueMaxValue は最大の正/負の "正規" 数を表しますが、実際の最小と最大の値は NegativeInfinityPositiveInfinity であるため、代わりにこれらの値にクランプされます。

演算子インターフェイス

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

  • 乗算や除算などの演算は、すべての型に対して正しいとは限らないため、明示的にはペアになりません。 たとえば、Matrix4x4 * Matrix4x4 は有効ですが、Matrix4x4 / Matrix4x4 は有効ではありません。
  • 通常、入力と結果の型は、2 つの整数を除算して double (3 / 2 = 1.5 など) を取得したり、整数のセットの平均を計算したりするなどのシナリオをサポートするために異なるものにすることができます。

注意

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

関数インターフェイス

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

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

たとえば、次のプログラムは入力として 2 つの数値を受け取り、型パラメーターが IParsable<TSelf> に制約されるジェネリック メソッドを使用してそれをコンソールから読み取ります。 入力と結果の値の型パラメーターが INumber<TSelf> に制約されるジェネリック メソッドを使用して平均を計算し、結果をコンソールに表示します。

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

関連項目