-
Notifications
You must be signed in to change notification settings - Fork 4.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[API Proposal]: Expand Complex Number support #80665
Comments
Tagging subscribers to this area: @dotnet/area-system-numerics Issue DetailsBackground and motivationThe current implementation of Complex in System.Numerics feels like a second-class citizen; it cannot be easily used in similar ways to other numeric types, and as it is backed by doubles it doesn't lend itself well to interop with Cuda, Intel IPP/MKL, or other math libraries for vector operations and performant processing. An expanded support of complex numbers could make .Net a more viable alternative to C++, Python, R, and Matlab code to users from science and engineer domains. API Proposalnamespace System.Numerics;
/*
My primary objective is to use a float32-backed complex number type.
It's implied the existing double-backed Complex type would be expanded uniformly, too.
This example is based on my use-case with Net Framework 4.7.2.
I feel it's not in the best interest of dotNet 7+ to adopt this verbatim;
instead, an implementation based on generic math would be more
appropriate. See 'Alternative Designs' section.
*/
[Serializable]
[StructLayout(LayoutKind.Sequential)]
public readonly struct Complex32 :
IEquatable<Complex32>,
IFormattable
{
private readonly float real;
private readonly float imaginary;
public float Real { get; }
public float Imaginary { get; }
public float Argument { get; }
public float Magnitude { get; }
public static readonly Complex32 Zero { get; }
public static readonly Complex32 One { get; }
public static readonly Complex32 ImaginaryOne { get; }
public static readonly Complex32 PositiveInfinity { get; }
public static readonly Complex32 NaN { get; }
public Complex32(float real, float imaginary);
public static Complex32 FromPolar(float magnitude, float argument);
public bool IsZero();
public bool IsOne();
public bool IsImaginaryOne();
public bool IsNaN();
public bool IsInfinity();
public bool IsReal();
public bool IsRealNonNegative();
public static bool operator ==(Complex32 left, Complex32 right);
public static bool operator !=(Complex32 left, Complex32 right);
public static Complex32 operator +(Complex32 current);
public static Complex32 operator -(Complex32 current);
public static Complex32 operator +(Complex32 left, Complex32 right);
public static Complex32 operator -(Complex32 left, Complex32 right);
public static Complex32 operator +(Complex32 left, float right);
public static Complex32 operator -(Complex32 left, float right);
public static Complex32 operator +(float left, Complex32 right);
public static Complex32 operator -(float left, Complex32 right);
public static Complex32 operator *(Complex32 left, Complex32 right);
public static Complex32 operator *(float left, Complex32 right);
public static Complex32 operator *(Complex32 left, float right);
public static Complex32 operator /(Complex32 left, Complex32 right);
public static Complex32 operator /(float left, Complex32 right);
public static Complex32 operator /(Complex32 left, float right);
public Complex ToComplex() => new Complex(real, imaginary);
// The current complex implementation formats as ordinals by default (RE, IM)
// This should instead format as 'RE+IMi', or some variant.
public override string ToString();
public string ToString(string format, IFormatProvider formatProvider);
public override int GetHashCode()
public bool Equals(Complex32 other);
public override bool Equals(object obj);
// The current Complex implementation does not have a Parse method.
// Parsing this is a little trickier than existing numerics.
public static Complex32 Parse(string value);
public static bool TryParse(string value, out Complex32 result);
public static implicit operator Complex32(byte value);
public static implicit operator Complex32(short value);
public static implicit operator Complex32(sbyte value);
public static implicit operator Complex32(ushort value);
public static implicit operator Complex32(int value);
public static implicit operator Complex32(BigInteger value);
public static implicit operator Complex32(long value);
public static implicit operator Complex32(uint value);
public static implicit operator Complex32(ulong value);
public static implicit operator Complex32(float value);
public static explicit operator Complex32(double value);
public static explicit operator Complex32(decimal value);
public static explicit operator Complex32(Complex value);
// There is also no support in the Math.X set of operators...
// ...it would make sense to add support for operations there, if not:
public Complex32 Exponential();
public Complex32 NaturalLogarithm();
public Complex32 Logarithm(float logBase);
public Complex32 SquareRoot();
public Complex32 Power(Complex32 exponent);
public Complex32 Conjugate();
public Complex32 Reciprocal();
public static Complex32 Negate(Complex32 value);
public static Complex32 Conjugate(Complex32 value);
public static Complex32 Add(Complex32 left, Complex32 right);
public static Complex32 Subtract(Complex32 left, Complex32 right);
public static Complex32 Multiply(Complex32 left, Complex32 right);
public static Complex32 Divide(Complex32 dividend, Complex32 divisor);
public static Complex32 Reciprocal(Complex32 value);
public static Complex32 Sqrt(Complex32 value);
public static float Abs(Complex32 value);
public static Complex32 Exp(Complex32 value);
public static Complex32 Pow(Complex32 value, Complex32 power);
public static Complex32 Pow(Complex32 value, float power);
public static Complex32 Ln(Complex32 value);
public static Complex32 Log(Complex32 value, float baseValue);
} API Usage// Simplify complex math operations.
var a = new Complex32(2.0f, 3.0f); // 2+3i
var b = new Complex32(5.0f, 2.0f); // 5+2i
// Division is otherwise complicated to implement...
var c = a/b; // eg. (2+3i)/(5+2i) => ...use conjugate... => 16/29 + 11/29 i
// Parsing is otherwise difficult for a user to implement, especially with localisation:
var s1 = "2+3i";
var c1 = Complex32.Parse(s1); // ...instead of tokenising and parsing elements manually - especially when accounting for localisation!
// Ideally this wouldn't be in ordinal form.
// When in ordinal form it's not obvious that this even is a complex number...
var s2 = c1.Conjugate().ToString(); // 2-3i
// However I predominantly just want a native type I can debug/develop with, but also hand off to other libraries/tools without difficult conversions.
// Adherence to an 8 byte (2x float, conventionally Real then Imaginary) structure ensures compatibility with types across most math/computational libraries, without adopting any specific one as a first party dependency.
var c2Array = new Complex32[100];
var c3Array = new Complex32[100];
...
IppLibraryImplementation.SomeFunction(c2Array, c3Array);
CudaLibraryImplementation.SomeFunction(c2Array, c3Array); Alternative DesignsI am predominantly using Net Framework (🥲), and unfamiliar with the current state of Generic Math in dotNet; I feel a better solution would be to instead implement a generic namespace System.Numerics;
[Serializable]
[StructLayout(LayoutKind.Sequential)]
public readonly struct Complex<T> :
...
where T : INumber<T> // <- How do we address this in Generic Math? 😭
{
private readonly T real;
private readonly T imaginary;
public T Real { get; }
public T Imaginary { get; }
...
} Risks
|
To clarify, would a float32-backed complex value be enough to interop with all of these other frameworks? If so, I think That aside, I can briefly speak on what Generic Math integration might look like, and @tannergooding can weigh in on this as well. From a Generic Math perspective, it would be less important to create a generic complex type such as public readonly struct Complex32
: IEquatable<Complex32>,
IFormattable,
INumberBase<Complex32>,
ISignedNumber<Complex32>
{
} It could also be beneficial to "hoist" the parts of both public interface IComplex<TSelf>
: INumberBase<TSelf>,
ISignedNumber<TSelf>
where TSelf : IComplex<TSelf>
{
// This interface would include methods shared by all Complex numbers that aren't already exposed by the above interfaces
}
public readonly struct Complex
: IEquatable<Complex>,
IFormattable,
IComplex<Complex>
{
}
public readonly struct Complex32
: IEquatable<Complex32>,
IFormattable,
IComplex<Complex32>
{
} This would allow you to write a single generic algorithm that could operate on both |
Yesn't.
I think there is importance on both sides. The type exposed needs to support the relevant interfaces so devs can use them generically, but having a singular |
@dakersnar I think the significance of float32 is that GPUs typically don't have equal capacity for double precision operations. With an up-market consumer GPU, I can write a kernel in CUDA using floats and work with 38.71 TFLOPS, or doubles at only 1.21 TFLOPS. Float16s in this context might be appealing from a memory perspective, but typically performance is uniform to float32s on most hardware I have seen. This isn't going to vary 'library to library'. My concerns aren't really about just any third party math library; libraries like Math.NET have some great ideas, but ultimately it's not going to improve the products I work on in the same way as being able to access vector intrinsics though Intel IPP/MKL or BLAS will, and all of these currently have their place, with large and opinionated APIs. I'd love to be able to access similar performance as 'Type explosion' would be one of my primary concerns (it kind of feels like power creep in a game 😅). This could be my ignorance from not having actually used any generic math features yet - I feel there is merit in not just being able to write an algorithm of Realistically before I can contribute any value to this discussion I need to invest some time in becoming comfortable with what is already available in dotNet 7, and a sense of how complex number support could add value to what is already there. |
Basically
The first means you can use the type the same as The second means you can write one algorithm that works with any The third means you can write one algorithm that supports any number type, whether There is then a few variations on the third. For example if we defined some We don't have anything like Higher Kinded Types which would allow you to say that |
This would definitely have use in graphics and image processing. In Paint.NET 5.0, there's a new Bokeh blur effect I added with collaboration from @Sergio0694 and @mikepound. It uses We'd need to add some logic to ComputeSharp so it can auto-marshal |
This absolutes interests me. I'm writing some DSP code and a lack of a Also, if F# is being pushed as a data-science, maybe numerics, and a performance-oriented language, the type is a must-have... |
Background and motivation
The current implementation of Complex in System.Numerics feels like a second-class citizen; it cannot be easily used in similar ways to other numeric types, and as it is backed by doubles it doesn't lend itself well to interop with Cuda, Intel IPP/MKL, or other math libraries for vector operations and performant processing.
An expanded support of complex numbers could make .Net a more viable alternative to C++, Python, R, and Matlab code to users from science and engineer domains.
API Proposal
API Usage
Alternative Designs
I am predominantly using Net Framework (🥲), and unfamiliar with the current state of Generic Math in dotNet; I feel a better solution would be to instead implement a generic
Complex<T>
that is treated with similar importance as float/integer types.Risks
Complex32
does not feel to align with the current conventions of .Net. I'm not sure what a more appropriate alternative might be in the context of .Net Framework, and dotnet < 7.0.Complex32
feels to not align with the direction of dotnet 7+. A genericComplex<T>
implementation seems more appropriate and less opinionated.The text was updated successfully, but these errors were encountered: