泛型数学

.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> 公开对二进制整数通用的 API2
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.MinValueT.MaxValue 的概念。
IMultiplicativeIdentity<TSelf,TResult> 公开 (x * T.MultiplicativeIdentity) == x 的概念。

1二进制浮点类型Double (double)、HalfSingle (float)。

2二进制整数类型Byte (byte)、Int16 (short)、Int32 (int)、Int64 (long)、Int128IntPtr (nint)、SByte (sbyte)、UInt16 (ushort)、UInt32 (uint)、UInt64 (ulong)、UInt128UIntPtr (nuint)。

最有可能直接使用的接口是 INumber<TSelf>,它大致对应于一个实数。 如果某个类型实现了此接口,则意味着一个值有一个符号(这包括 unsigned 类型,这些类型被认为是正数)并且可以与相同类型的其他值进行比较。 INumberBase<TSelf> 提供复数和虚数等更高级的概念,例如负数的平方根。 创建其他接口(例如 IFloatingPointIeee754<TSelf>)是因为并非所有操作都对所有数字类型都有意义。例如,计算数字的下限仅对浮点类型有意义。 在 .NET 基类库中,浮点类型 Double 实现 IFloatingPointIeee754<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 返回两个值中的较大值,如果任一输入为 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 创建一个值,如果输入不合适,则引发 OverflowException1
CreateSaturating 创建一个值,如果输入不合适,则钳制为 T.MinValueT.MaxValue1
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 浮点类型也有一些特殊考虑,例如 floatdouble,因为它们具有特殊值 PositiveInfinityNegativeInfinityNaN。 所有三个 Create* API 都表现为 CreateSaturating。 此外,虽然 MinValueMaxValue 表示最大的负/正“正常”数,但实际的最小值和最大值是 NegativeInfinityPositiveInfinity,因此它们改为钳制这些值。

运算符接口

运算符接口对应于可用于 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 < yx > yx <= yx >= 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 运算符。 Checked 运算符在 checked 上下文中调用,并允许用户定义的类型定义溢出行为。 如果实现了 checked 运算符,例如 CheckedSubtraction(TSelf, TOther),则还必须实现 unchecked 的运算符,例如 Subtraction(TSelf, TOther)

函数接口

函数接口定义了比特定数值接口更广泛应用的泛型数学 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 的逆运算。

例如,以下程序将两个数字作为输入,使用类型参数限制为 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
*/

另请参阅