泛型数学

.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 中的接口,这些接口描述类似数字的类型及其可用的功能。

接口名称 DESCRIPTION
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.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 名称 DESCRIPTION
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 创建一个值,如果输入不合适,则引发 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 无效。
  • 它们通常允许输入和结果类型有所不同,以支持方案,例如,将两个整数除以获取一个double3 / 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

注释

某些接口除了定义一个常规的未检查运算符外,还定义了一个检查过的运算符。 选中的运算符在已检查的上下文中调用,并允许用户定义的类型定义溢出行为。 如果实现了 checked 运算符,例如 CheckedSubtraction(TSelf, TOther),则还必须实现 unchecked 的运算符,例如 Subtraction(TSelf, TOther)。

函数接口

函数接口定义比应用于特定 数值接口更广泛的常见数学 API。 这些接口都是由IFloatingPointIeee754<TSelf>实现的,将来可能会由其他相关类型实现。

接口名称 DESCRIPTION
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 。

接口名称 DESCRIPTION
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的反运算。

例如,以下程序采用两个数字作为输入,使用泛型方法从控制台读取它们,其中类型参数受到限制 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
*/

另请参阅