一般數學
.NET 7 將新的數學相關泛型介面引進基底類別庫。 這些介面的可用性表示您可以將泛型型別或方法的型別參數限制為「類似數字」。 此外,C# 11 和更新版本可讓您定義 static virtual
介面成員。 由於運算子必須宣告為 static
,所以這個新的 C# 功能可讓運算子在類似數字類型的新介面中進行宣告。
這些創新功能可讓您一般執行數學運算,也就是說,不需要知道您所使用的確切類型。 例如,如果您想要撰寫一個新增兩個數字的方法,則先前必須為每個型別新增方法的多載 (例如,static int Add(int first, int second)
和 static float Add(float first, float second)
)。 現在您可以撰寫單一泛型方法,其中 type 參數受限為類似數字的型別。 例如:
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> | 公開所有帶正負號數字類型的通用 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二進位浮點類型為 Double (double
)、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 |
取得值,表示類型的 Euler 數字。 |
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 |
如果值並非無限且非 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。 這表示 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 位元則為0x80
,也就是 128)。
假設值太小時的範例:
byte.CreateChecked(-384)
將會擲回 OverflowException。byte.CreateSaturating(-384)
會傳回 0,因為 -384 小於 Byte.MinValue (也就是 0)。byte.CreateTruncating(-384)
會傳回 128,因為其採用最低 8 位元 (384 具有十六進位表示法0xFE80
,而最低 8 位元則為0x80
,也就是 128)。
Create*
方法針對 IEEE 754 浮點類型也有一些特殊考量 (例如 float
和 double
),因為其具有特殊值 PositiveInfinity
、NegativeInfinity
和 NaN
。 這三個 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) 的支援。 |
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
*/