diff --git a/src/libraries/System.Runtime.Numerics/ref/System.Runtime.Numerics.cs b/src/libraries/System.Runtime.Numerics/ref/System.Runtime.Numerics.cs index e080d40f99ce11..b8281f0df2a5b7 100644 --- a/src/libraries/System.Runtime.Numerics/ref/System.Runtime.Numerics.cs +++ b/src/libraries/System.Runtime.Numerics/ref/System.Runtime.Numerics.cs @@ -399,4 +399,128 @@ namespace System.Numerics public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.Numerics.Complex result) { throw null; } public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.IFormatProvider? provider, out System.Numerics.Complex result) { throw null; } } + public readonly partial struct Complex : System.IEquatable>, System.IFormattable, System.IParsable>, System.ISpanFormattable, System.ISpanParsable>, System.IUtf8SpanFormattable, System.IUtf8SpanParsable>, System.Numerics.IAdditionOperators, System.Numerics.Complex, System.Numerics.Complex>, System.Numerics.IAdditiveIdentity, System.Numerics.Complex>, System.Numerics.IDecrementOperators>, System.Numerics.IDivisionOperators, System.Numerics.Complex, System.Numerics.Complex>, System.Numerics.IEqualityOperators, System.Numerics.Complex, bool>, System.Numerics.IIncrementOperators>, System.Numerics.IMultiplicativeIdentity, System.Numerics.Complex>, System.Numerics.IMultiplyOperators, System.Numerics.Complex, System.Numerics.Complex>, System.Numerics.INumberBase>, System.Numerics.ISignedNumber>, System.Numerics.ISubtractionOperators, System.Numerics.Complex, System.Numerics.Complex>, System.Numerics.IUnaryNegationOperators, System.Numerics.Complex>, System.Numerics.IUnaryPlusOperators, System.Numerics.Complex> + where T : System.Numerics.IFloatingPointIeee754, System.Numerics.IMinMaxValue + { + private readonly int _dummyPrimitive; + public static System.Numerics.Complex ImaginaryOne { get { throw null; } } + public static System.Numerics.Complex Infinity { get { throw null; } } + public static System.Numerics.Complex NaN { get { throw null; } } + public static System.Numerics.Complex One { get { throw null; } } + public static System.Numerics.Complex Zero { get { throw null; } } + public Complex(T real, T imaginary) { throw null; } + public T Imaginary { get { throw null; } } + public T Real { get { throw null; } } + static System.Numerics.Complex System.Numerics.IAdditiveIdentity,System.Numerics.Complex>.AdditiveIdentity { get { throw null; } } + static System.Numerics.Complex System.Numerics.IMultiplicativeIdentity,System.Numerics.Complex>.MultiplicativeIdentity { get { throw null; } } + static int System.Numerics.INumberBase>.Radix { get { throw null; } } + static System.Numerics.Complex System.Numerics.ISignedNumber>.NegativeOne { get { throw null; } } + public static T Abs(System.Numerics.Complex value) { throw null; } + public static System.Numerics.Complex Acos(System.Numerics.Complex value) { throw null; } + public static System.Numerics.Complex Add(T left, System.Numerics.Complex right) { throw null; } + public static System.Numerics.Complex Add(System.Numerics.Complex left, T right) { throw null; } + public static System.Numerics.Complex Add(System.Numerics.Complex left, System.Numerics.Complex right) { throw null; } + public static System.Numerics.Complex Asin(System.Numerics.Complex value) { throw null; } + public static System.Numerics.Complex Atan(System.Numerics.Complex value) { throw null; } + public static System.Numerics.Complex Conjugate(System.Numerics.Complex value) { throw null; } + public static System.Numerics.Complex Cos(System.Numerics.Complex value) { throw null; } + public static System.Numerics.Complex Cosh(System.Numerics.Complex value) { throw null; } + public static System.Numerics.Complex CreateChecked(TOther value) where TOther : System.Numerics.INumberBase { throw null; } + public static System.Numerics.Complex CreateSaturating(TOther value) where TOther : System.Numerics.INumberBase { throw null; } + public static System.Numerics.Complex CreateTruncating(TOther value) where TOther : System.Numerics.INumberBase { throw null; } + public static System.Numerics.Complex Divide(T dividend, System.Numerics.Complex divisor) { throw null; } + public static System.Numerics.Complex Divide(System.Numerics.Complex dividend, T divisor) { throw null; } + public static System.Numerics.Complex Divide(System.Numerics.Complex dividend, System.Numerics.Complex divisor) { throw null; } + public bool Equals(System.Numerics.Complex value) { throw null; } + public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] object? obj) { throw null; } + public static System.Numerics.Complex Exp(System.Numerics.Complex value) { throw null; } + public static System.Numerics.Complex FromPolarCoordinates(T magnitude, T phase) { throw null; } + public override int GetHashCode() { throw null; } + public T GetMagnitude() { throw null; } + public T GetPhase() { throw null; } + public static bool IsComplexNumber(System.Numerics.Complex value) { throw null; } + public static bool IsEvenInteger(System.Numerics.Complex value) { throw null; } + public static bool IsFinite(System.Numerics.Complex value) { throw null; } + public static bool IsImaginaryNumber(System.Numerics.Complex value) { throw null; } + public static bool IsInfinity(System.Numerics.Complex value) { throw null; } + public static bool IsInteger(System.Numerics.Complex value) { throw null; } + public static bool IsNaN(System.Numerics.Complex value) { throw null; } + public static bool IsNegative(System.Numerics.Complex value) { throw null; } + public static bool IsNegativeInfinity(System.Numerics.Complex value) { throw null; } + public static bool IsNormal(System.Numerics.Complex value) { throw null; } + public static bool IsOddInteger(System.Numerics.Complex value) { throw null; } + public static bool IsPositive(System.Numerics.Complex value) { throw null; } + public static bool IsPositiveInfinity(System.Numerics.Complex value) { throw null; } + public static bool IsRealNumber(System.Numerics.Complex value) { throw null; } + public static bool IsSubnormal(System.Numerics.Complex value) { throw null; } + public static System.Numerics.Complex Log(System.Numerics.Complex value) { throw null; } + public static System.Numerics.Complex Log(System.Numerics.Complex value, T baseValue) { throw null; } + public static System.Numerics.Complex Log10(System.Numerics.Complex value) { throw null; } + public static System.Numerics.Complex MaxMagnitude(System.Numerics.Complex x, System.Numerics.Complex y) { throw null; } + public static System.Numerics.Complex MinMagnitude(System.Numerics.Complex x, System.Numerics.Complex y) { throw null; } + public static System.Numerics.Complex Multiply(T left, System.Numerics.Complex right) { throw null; } + public static System.Numerics.Complex Multiply(System.Numerics.Complex left, T right) { throw null; } + public static System.Numerics.Complex Multiply(System.Numerics.Complex left, System.Numerics.Complex right) { throw null; } + public static System.Numerics.Complex Negate(System.Numerics.Complex value) { throw null; } + public static System.Numerics.Complex operator +(T left, System.Numerics.Complex right) { throw null; } + public static System.Numerics.Complex operator +(System.Numerics.Complex left, T right) { throw null; } + public static System.Numerics.Complex operator +(System.Numerics.Complex left, System.Numerics.Complex right) { throw null; } + public static System.Numerics.Complex operator --(System.Numerics.Complex value) { throw null; } + public static System.Numerics.Complex operator /(T left, System.Numerics.Complex right) { throw null; } + public static System.Numerics.Complex operator /(System.Numerics.Complex left, T right) { throw null; } + public static System.Numerics.Complex operator /(System.Numerics.Complex left, System.Numerics.Complex right) { throw null; } + public static bool operator ==(System.Numerics.Complex left, System.Numerics.Complex right) { throw null; } + public static System.Numerics.Complex operator ++(System.Numerics.Complex value) { throw null; } + public static bool operator !=(System.Numerics.Complex left, System.Numerics.Complex right) { throw null; } + public static System.Numerics.Complex operator *(T left, System.Numerics.Complex right) { throw null; } + public static System.Numerics.Complex operator *(System.Numerics.Complex left, T right) { throw null; } + public static System.Numerics.Complex operator *(System.Numerics.Complex left, System.Numerics.Complex right) { throw null; } + public static System.Numerics.Complex operator -(T left, System.Numerics.Complex right) { throw null; } + public static System.Numerics.Complex operator -(System.Numerics.Complex left, T right) { throw null; } + public static System.Numerics.Complex operator -(System.Numerics.Complex left, System.Numerics.Complex right) { throw null; } + public static System.Numerics.Complex operator -(System.Numerics.Complex value) { throw null; } + public static System.Numerics.Complex operator +(System.Numerics.Complex value) { throw null; } + public static System.Numerics.Complex Parse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style, System.IFormatProvider? provider) { throw null; } + public static System.Numerics.Complex Parse(System.ReadOnlySpan utf8Text, System.IFormatProvider? provider) { throw null; } + public static System.Numerics.Complex Parse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider) { throw null; } + public static System.Numerics.Complex Parse(System.ReadOnlySpan s, System.IFormatProvider? provider) { throw null; } + public static System.Numerics.Complex Parse(string s, System.Globalization.NumberStyles style, System.IFormatProvider? provider) { throw null; } + public static System.Numerics.Complex Parse(string s, System.IFormatProvider? provider) { throw null; } + public static System.Numerics.Complex Pow(System.Numerics.Complex value, T power) { throw null; } + public static System.Numerics.Complex Pow(System.Numerics.Complex value, System.Numerics.Complex power) { throw null; } + public static System.Numerics.Complex Reciprocal(System.Numerics.Complex value) { throw null; } + public static System.Numerics.Complex Sin(System.Numerics.Complex value) { throw null; } + public static System.Numerics.Complex Sinh(System.Numerics.Complex value) { throw null; } + public static System.Numerics.Complex Sqrt(System.Numerics.Complex value) { throw null; } + public static System.Numerics.Complex Subtract(T left, System.Numerics.Complex right) { throw null; } + public static System.Numerics.Complex Subtract(System.Numerics.Complex left, T right) { throw null; } + public static System.Numerics.Complex Subtract(System.Numerics.Complex left, System.Numerics.Complex right) { throw null; } + static System.Numerics.Complex System.Numerics.INumberBase>.Abs(System.Numerics.Complex value) { throw null; } + static bool System.Numerics.INumberBase>.IsCanonical(System.Numerics.Complex value) { throw null; } + static bool System.Numerics.INumberBase>.IsZero(System.Numerics.Complex value) { throw null; } + static System.Numerics.Complex System.Numerics.INumberBase>.MaxMagnitudeNumber(System.Numerics.Complex x, System.Numerics.Complex y) { throw null; } + static System.Numerics.Complex System.Numerics.INumberBase>.MinMagnitudeNumber(System.Numerics.Complex x, System.Numerics.Complex y) { throw null; } + static System.Numerics.Complex System.Numerics.INumberBase>.MultiplyAddEstimate(System.Numerics.Complex left, System.Numerics.Complex right, System.Numerics.Complex addend) { throw null; } + static bool System.Numerics.INumberBase>.TryConvertFromChecked(TOther value, out System.Numerics.Complex result) { throw null; } + static bool System.Numerics.INumberBase>.TryConvertFromSaturating(TOther value, out System.Numerics.Complex result) { throw null; } + static bool System.Numerics.INumberBase>.TryConvertFromTruncating(TOther value, out System.Numerics.Complex result) { throw null; } + static bool System.Numerics.INumberBase>.TryConvertToChecked(System.Numerics.Complex value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase>.TryConvertToSaturating(System.Numerics.Complex value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + static bool System.Numerics.INumberBase>.TryConvertToTruncating(System.Numerics.Complex value, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TOther result) { throw null; } + public static System.Numerics.Complex Tan(System.Numerics.Complex value) { throw null; } + public static System.Numerics.Complex Tanh(System.Numerics.Complex value) { throw null; } + public override string ToString() { throw null; } + public string ToString([System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] string? format) { throw null; } + public string ToString(System.IFormatProvider? provider) { throw null; } + public string ToString([System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] string? format, System.IFormatProvider? provider) { throw null; } + public bool TryFormat(System.Span utf8Destination, out int bytesWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format = default, System.IFormatProvider? provider = null) { throw null; } + public bool TryFormat(System.Span destination, out int charsWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format = default, System.IFormatProvider? provider = null) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.Numerics.Complex result) { throw null; } + public static bool TryParse(System.ReadOnlySpan utf8Text, System.IFormatProvider? provider, out System.Numerics.Complex result) { throw null; } + public static bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.Numerics.Complex result) { throw null; } + public static bool TryParse(System.ReadOnlySpan s, System.IFormatProvider? provider, out System.Numerics.Complex result) { throw null; } + public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.Numerics.Complex result) { throw null; } + public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.IFormatProvider? provider, out System.Numerics.Complex result) { throw null; } + public static implicit operator System.Numerics.Complex(T value) { throw null; } + } } diff --git a/src/libraries/System.Runtime.Numerics/src/System.Runtime.Numerics.csproj b/src/libraries/System.Runtime.Numerics/src/System.Runtime.Numerics.csproj index e6b91e1990ea09..80502488f51ff3 100644 --- a/src/libraries/System.Runtime.Numerics/src/System.Runtime.Numerics.csproj +++ b/src/libraries/System.Runtime.Numerics/src/System.Runtime.Numerics.csproj @@ -24,6 +24,7 @@ + diff --git a/src/libraries/System.Runtime.Numerics/src/System/Numerics/Complex.Generic.cs b/src/libraries/System.Runtime.Numerics/src/System/Numerics/Complex.Generic.cs new file mode 100644 index 00000000000000..ac467e4a7aebbb --- /dev/null +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/Complex.Generic.cs @@ -0,0 +1,1654 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Buffers; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; + +namespace System.Numerics +{ + /// + /// A generic complex number z = x + yi, where x and y are of type . + /// + /// The floating-point type used for the real and imaginary components. + public readonly struct Complex + : IEquatable>, + IFormattable, + INumberBase>, + ISignedNumber>, + IUtf8SpanFormattable + where T : IFloatingPointIeee754, IMinMaxValue + { + public static Complex Zero => new(T.Zero, T.Zero); + public static Complex One => new(T.One, T.Zero); + public static Complex ImaginaryOne => new(T.Zero, T.One); + public static Complex NaN => new(T.NaN, T.NaN); + public static Complex Infinity => new(T.PositiveInfinity, T.PositiveInfinity); + + // 1 / Log(10) + private static readonly T s_inverseOfLog10 = T.One / T.Log(T.CreateChecked(10)); + + // This is the largest x for which (Hypot(x,x) + x) will not overflow. It is used for branching inside Sqrt. + private static readonly T s_sqrtRescaleThreshold = T.MaxValue / (T.Sqrt(T.CreateChecked(2)) + T.One); + + // This is the largest x for which 2 x^2 will not overflow. It is used for branching inside Asin and Acos. + private static readonly T s_asinOverflowThreshold = T.Sqrt(T.MaxValue) / T.CreateChecked(2); + + // This value is used inside Asin and Acos. + private static readonly T s_log2 = T.Log(T.CreateChecked(2)); + + private readonly T m_real; + private readonly T m_imaginary; + + public Complex(T real, T imaginary) + { + m_real = real; + m_imaginary = imaginary; + } + + public T Real => m_real; + public T Imaginary => m_imaginary; + + public T GetMagnitude() => Abs(this); + public T GetPhase() => T.Atan2(m_imaginary, m_real); + + public static Complex FromPolarCoordinates(T magnitude, T phase) + { + (T sin, T cos) = T.SinCos(phase); + return new Complex(magnitude * cos, magnitude * sin); + } + + public static Complex Negate(Complex value) + { + return -value; + } + + public static Complex Add(Complex left, Complex right) + { + return left + right; + } + + public static Complex Add(Complex left, T right) + { + return left + right; + } + + public static Complex Add(T left, Complex right) + { + return left + right; + } + + public static Complex Subtract(Complex left, Complex right) + { + return left - right; + } + + public static Complex Subtract(Complex left, T right) + { + return left - right; + } + + public static Complex Subtract(T left, Complex right) + { + return left - right; + } + + public static Complex Multiply(Complex left, Complex right) + { + return left * right; + } + + public static Complex Multiply(Complex left, T right) + { + return left * right; + } + + public static Complex Multiply(T left, Complex right) + { + return left * right; + } + + public static Complex Divide(Complex dividend, Complex divisor) + { + return dividend / divisor; + } + + public static Complex Divide(Complex dividend, T divisor) + { + return dividend / divisor; + } + + public static Complex Divide(T dividend, Complex divisor) + { + return dividend / divisor; + } + + public static Complex operator -(Complex value) + { + return new Complex(-value.m_real, -value.m_imaginary); + } + + public static Complex operator +(Complex left, Complex right) + { + return new Complex(left.m_real + right.m_real, left.m_imaginary + right.m_imaginary); + } + + public static Complex operator +(Complex left, T right) + { + return new Complex(left.m_real + right, left.m_imaginary); + } + + public static Complex operator +(T left, Complex right) + { + return new Complex(left + right.m_real, right.m_imaginary); + } + + public static Complex operator -(Complex left, Complex right) + { + return new Complex(left.m_real - right.m_real, left.m_imaginary - right.m_imaginary); + } + + public static Complex operator -(Complex left, T right) + { + return new Complex(left.m_real - right, left.m_imaginary); + } + + public static Complex operator -(T left, Complex right) + { + return new Complex(left - right.m_real, -right.m_imaginary); + } + + public static Complex operator *(Complex left, Complex right) + { + // Multiplication: (a + bi)(c + di) = (ac - bd) + (bc + ad)i + T result_realpart = (left.m_real * right.m_real) - (left.m_imaginary * right.m_imaginary); + T result_imaginarypart = (left.m_imaginary * right.m_real) + (left.m_real * right.m_imaginary); + return new Complex(result_realpart, result_imaginarypart); + } + + public static Complex operator *(Complex left, T right) + { + if (!T.IsFinite(left.m_real)) + { + if (!T.IsFinite(left.m_imaginary)) + { + return new Complex(T.NaN, T.NaN); + } + + return new Complex(left.m_real * right, T.NaN); + } + + if (!T.IsFinite(left.m_imaginary)) + { + return new Complex(T.NaN, left.m_imaginary * right); + } + + return new Complex(left.m_real * right, left.m_imaginary * right); + } + + public static Complex operator *(T left, Complex right) + { + if (!T.IsFinite(right.m_real)) + { + if (!T.IsFinite(right.m_imaginary)) + { + return new Complex(T.NaN, T.NaN); + } + + return new Complex(left * right.m_real, T.NaN); + } + + if (!T.IsFinite(right.m_imaginary)) + { + return new Complex(T.NaN, left * right.m_imaginary); + } + + return new Complex(left * right.m_real, left * right.m_imaginary); + } + + public static Complex operator /(Complex left, Complex right) + { + // Division : Smith's formula. + T a = left.m_real; + T b = left.m_imaginary; + T c = right.m_real; + T d = right.m_imaginary; + + // Computing c * c + d * d will overflow even in cases where the actual result of the division does not overflow. + if (T.Abs(d) < T.Abs(c)) + { + T doc = d / c; + return new Complex((a + b * doc) / (c + d * doc), (b - a * doc) / (c + d * doc)); + } + else + { + T cod = c / d; + return new Complex((b + a * cod) / (d + c * cod), (-a + b * cod) / (d + c * cod)); + } + } + + public static Complex operator /(Complex left, T right) + { + // IEEE prohibit optimizations which are value changing + // so we make sure that behaviour for the simplified version exactly match + // full version. + if (right == T.Zero) + { + return new Complex(T.NaN, T.NaN); + } + + if (!T.IsFinite(left.m_real)) + { + if (!T.IsFinite(left.m_imaginary)) + { + return new Complex(T.NaN, T.NaN); + } + + return new Complex(left.m_real / right, T.NaN); + } + + if (!T.IsFinite(left.m_imaginary)) + { + return new Complex(T.NaN, left.m_imaginary / right); + } + + // Here the actual optimized version of code. + return new Complex(left.m_real / right, left.m_imaginary / right); + } + + public static Complex operator /(T left, Complex right) + { + // Division : Smith's formula. + T a = left; + T c = right.m_real; + T d = right.m_imaginary; + + // Computing c * c + d * d will overflow even in cases where the actual result of the division does not overflow. + if (T.Abs(d) < T.Abs(c)) + { + T doc = d / c; + return new Complex(a / (c + d * doc), (-a * doc) / (c + d * doc)); + } + else + { + T cod = c / d; + return new Complex(a * cod / (d + c * cod), -a / (d + c * cod)); + } + } + + public static T Abs(Complex value) + { + return T.Hypot(value.m_real, value.m_imaginary); + } + + private static T Log1P(T x) + { + // Compute log(1 + x) without loss of accuracy when x is small. + + // Our only use case so far is for positive values, so this isn't coded to handle negative values. + Debug.Assert((x >= T.Zero) || T.IsNaN(x)); + + T xp1 = T.One + x; + if (xp1 == T.One) + { + return x; + } + else if (x < T.CreateChecked(0.75)) + { + // This is accurate to within 5 ulp with any floating-point system that uses a guard digit, + // as proven in Theorem 4 of "What Every Computer Scientist Should Know About Floating-Point + // Arithmetic" (https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html) + return x * T.Log(xp1) / (xp1 - T.One); + } + else + { + return T.Log(xp1); + } + } + + public static Complex Conjugate(Complex value) + { + // Conjugate of a Complex number: the conjugate of x+i*y is x-i*y + return new Complex(value.m_real, -value.m_imaginary); + } + + public static Complex Reciprocal(Complex value) + { + // Reciprocal of a Complex number : the reciprocal of x+i*y is 1/(x+i*y) + if (value.m_real == T.Zero && value.m_imaginary == T.Zero) + { + return Zero; + } + return One / value; + } + + public static bool operator ==(Complex left, Complex right) + { + return left.m_real == right.m_real && left.m_imaginary == right.m_imaginary; + } + + public static bool operator !=(Complex left, Complex right) + { + return left.m_real != right.m_real || left.m_imaginary != right.m_imaginary; + } + + public override bool Equals([NotNullWhen(true)] object? obj) + { + return obj is Complex other && Equals(other); + } + + public bool Equals(Complex value) + { + return m_real.Equals(value.m_real) && m_imaginary.Equals(value.m_imaginary); + } + + public override int GetHashCode() => HashCode.Combine(m_real, m_imaginary); + + public override string ToString() => ToString(null, null); + + public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format) => ToString(format, null); + + public string ToString(IFormatProvider? provider) => ToString(null, provider); + + public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format, IFormatProvider? provider) + { + // $"<{m_real.ToString(format, provider)}; {m_imaginary.ToString(format, provider)}>"; + var handler = new DefaultInterpolatedStringHandler(4, 2, provider, stackalloc char[512]); + handler.AppendLiteral("<"); + handler.AppendFormatted(m_real, format); + handler.AppendLiteral("; "); + handler.AppendFormatted(m_imaginary, format); + handler.AppendLiteral(">"); + return handler.ToStringAndClear(); + } + + public static Complex Sin(Complex value) + { + (T sin, T cos) = T.SinCos(value.m_real); + return new Complex(sin * T.Cosh(value.m_imaginary), cos * T.Sinh(value.m_imaginary)); + } + + public static Complex Sinh(Complex value) + { + // Use sinh(z) = -i sin(iz) to compute via sin(z). + Complex sin = Sin(new Complex(-value.m_imaginary, value.m_real)); + return new Complex(sin.m_imaginary, -sin.m_real); + } + + public static Complex Asin(Complex value) + { + Asin_Internal(T.Abs(value.Real), T.Abs(value.Imaginary), out T b, out T bPrime, out T v); + + T u; + if (bPrime < T.Zero) + { + u = T.Asin(b); + } + else + { + u = T.Atan(bPrime); + } + + if (value.Real < T.Zero) u = -u; + if (value.Imaginary < T.Zero) v = -v; + + return new Complex(u, v); + } + + public static Complex Cos(Complex value) + { + (T sin, T cos) = T.SinCos(value.m_real); + return new Complex(cos * T.Cosh(value.m_imaginary), -sin * T.Sinh(value.m_imaginary)); + } + + public static Complex Cosh(Complex value) + { + // Use cosh(z) = cos(iz) to compute via cos(z). + return Cos(new Complex(-value.m_imaginary, value.m_real)); + } + + public static Complex Acos(Complex value) + { + Asin_Internal(T.Abs(value.Real), T.Abs(value.Imaginary), out T b, out T bPrime, out T v); + + T u; + if (bPrime < T.Zero) + { + u = T.Acos(b); + } + else + { + u = T.Atan(T.One / bPrime); + } + + if (value.Real < T.Zero) u = T.Pi - u; + if (value.Imaginary > T.Zero) v = -v; + + return new Complex(u, v); + } + + public static Complex Tan(Complex value) + { + // tan z = sin z / cos z, but to avoid unnecessary repeated trig computations, use + // tan z = (sin(2x) + i sinh(2y)) / (cos(2x) + cosh(2y)) + // (see Abramowitz & Stegun 4.3.57 or derive by hand), and compute trig functions here. + + // This approach does not work for |y| > ~355, because sinh(2y) and cosh(2y) overflow, + // even though their ratio does not. In that case, divide through by cosh to get: + // tan z = (sin(2x) / cosh(2y) + i \tanh(2y)) / (1 + cos(2x) / cosh(2y)) + // which correctly computes the (tiny) real part and the (normal-sized) imaginary part. + + T two = T.CreateChecked(2); + T x2 = two * value.m_real; + T y2 = two * value.m_imaginary; + (T sin, T cos) = T.SinCos(x2); + T cosh = T.Cosh(y2); + if (T.Abs(value.m_imaginary) <= T.CreateChecked(4)) + { + T D = cos + cosh; + return new Complex(sin / D, T.Sinh(y2) / D); + } + else + { + T D = T.One + cos / cosh; + return new Complex(sin / cosh / D, T.Tanh(y2) / D); + } + } + + public static Complex Tanh(Complex value) + { + // Use tanh(z) = -i tan(iz) to compute via tan(z). + Complex tan = Tan(new Complex(-value.m_imaginary, value.m_real)); + return new Complex(tan.m_imaginary, -tan.m_real); + } + + public static Complex Atan(Complex value) + { + Complex two = new(T.CreateChecked(2), T.Zero); + return (ImaginaryOne / two) * (Log(One - ImaginaryOne * value) - Log(One + ImaginaryOne * value)); + } + + private static void Asin_Internal(T x, T y, out T b, out T bPrime, out T v) + { + // This method for the inverse complex sine (and cosine) is described in Hull, Fairgrieve, + // and Tang, "Implementing the Complex Arcsine and Arccosine Functions Using Exception Handling", + // ACM Transactions on Mathematical Software (1997) + // (https://www.researchgate.net/profile/Ping_Tang3/publication/220493330_Implementing_the_Complex_Arcsine_and_Arccosine_Functions_Using_Exception_Handling/links/55b244b208ae9289a085245d.pdf) + + Debug.Assert((x >= T.Zero) || T.IsNaN(x)); + Debug.Assert((y >= T.Zero) || T.IsNaN(y)); + + if ((x > s_asinOverflowThreshold) || (y > s_asinOverflowThreshold)) + { + b = -T.One; + bPrime = x / y; + + T small, big; + if (x < y) + { + small = x; + big = y; + } + else + { + small = y; + big = x; + } + T ratio = small / big; + v = s_log2 + T.Log(big) + Log1P(ratio * ratio) / T.CreateChecked(2); + } + else + { + T r = T.Hypot(x + T.One, y); + T s = T.Hypot(x - T.One, y); + + T a = (r + s) / T.CreateChecked(2); + b = x / a; + + if (b > T.CreateChecked(0.75)) + { + if (x <= T.One) + { + T amx = (y * y / (r + (x + T.One)) + (s + (T.One - x))) / T.CreateChecked(2); + bPrime = x / T.Sqrt((a + x) * amx); + } + else + { + // In this case, amx ~ y^2. Since we take the square root of amx, we should + // pull y out from under the square root so we don't lose its contribution + // when y^2 underflows. + T t = (T.One / (r + (x + T.One)) + T.One / (s + (x - T.One))) / T.CreateChecked(2); + bPrime = x / y / T.Sqrt((a + x) * t); + } + } + else + { + bPrime = -T.One; + } + + if (a < T.CreateChecked(1.5)) + { + if (x < T.One) + { + // This is another case where our expression is proportional to y^2 and + // we take its square root, so again we pull out a factor of y from + // under the square root. + T t = (T.One / (r + (x + T.One)) + T.One / (s + (T.One - x))) / T.CreateChecked(2); + T am1 = y * y * t; + v = Log1P(am1 + y * T.Sqrt(t * (a + T.One))); + } + else + { + T am1 = (y * y / (r + (x + T.One)) + (s + (x - T.One))) / T.CreateChecked(2); + v = Log1P(am1 + T.Sqrt(am1 * (a + T.One))); + } + } + else + { + // Because of the test above, we can be sure that a * a will not overflow. + v = T.Log(a + T.Sqrt((a - T.One) * (a + T.One))); + } + } + } + + public static bool IsFinite(Complex value) => T.IsFinite(value.m_real) && T.IsFinite(value.m_imaginary); + + public static bool IsInfinity(Complex value) => T.IsInfinity(value.m_real) || T.IsInfinity(value.m_imaginary); + + public static bool IsNaN(Complex value) => !IsInfinity(value) && !IsFinite(value); + + public static Complex Log(Complex value) + { + return new Complex(T.Log(Abs(value)), T.Atan2(value.m_imaginary, value.m_real)); + } + + public static Complex Log(Complex value, T baseValue) + { + return Log(value) / Log(new Complex(baseValue, T.Zero)); + } + + public static Complex Log10(Complex value) + { + Complex tempLog = Log(value); + return Scale(tempLog, s_inverseOfLog10); + } + + public static Complex Exp(Complex value) + { + T expReal = T.Exp(value.m_real); + return FromPolarCoordinates(expReal, value.m_imaginary); + } + + public static Complex Sqrt(Complex value) + { + // Handle NaN input cases according to IEEE 754 + if (T.IsNaN(value.m_real)) + { + if (T.IsInfinity(value.m_imaginary)) + { + return new Complex(T.PositiveInfinity, value.m_imaginary); + } + return new Complex(T.NaN, T.NaN); + } + if (T.IsNaN(value.m_imaginary)) + { + if (T.IsPositiveInfinity(value.m_real)) + { + return new Complex(T.NaN, T.PositiveInfinity); + } + if (T.IsNegativeInfinity(value.m_real)) + { + return new Complex(T.PositiveInfinity, T.NaN); + } + return new Complex(T.NaN, T.NaN); + } + + if (value.m_imaginary == T.Zero) + { + // Handle the trivial case quickly. + if (value.m_real < T.Zero) + { + return new Complex(T.Zero, T.Sqrt(-value.m_real)); + } + + return new Complex(T.Sqrt(value.m_real), T.Zero); + } + + // If the components are too large, Hypot will overflow, even though the subsequent sqrt would + // make the result representable. To avoid this, we re-scale (by exact powers of 2 for accuracy) + // when we encounter very large components to avoid intermediate infinities. + bool rescale = false; + T realCopy = value.m_real; + T imaginaryCopy = value.m_imaginary; + if ((T.Abs(realCopy) >= s_sqrtRescaleThreshold) || (T.Abs(imaginaryCopy) >= s_sqrtRescaleThreshold)) + { + if (T.IsInfinity(value.m_imaginary)) + { + // We need to handle infinite imaginary parts specially because otherwise + // our formulas below produce inf/inf = NaN. + return new Complex(T.PositiveInfinity, imaginaryCopy); + } + + T quarter = T.CreateChecked(0.25); + realCopy *= quarter; + imaginaryCopy *= quarter; + rescale = true; + } + + // This is the core of the algorithm. Everything else is special case handling. + T x, y; + T half = T.CreateChecked(0.5); + if (realCopy >= T.Zero) + { + x = T.Sqrt((T.Hypot(realCopy, imaginaryCopy) + realCopy) * half); + y = imaginaryCopy / (T.CreateChecked(2) * x); + } + else + { + y = T.Sqrt((T.Hypot(realCopy, imaginaryCopy) - realCopy) * half); + if (imaginaryCopy < T.Zero) y = -y; + x = imaginaryCopy / (T.CreateChecked(2) * y); + } + + if (rescale) + { + x *= T.CreateChecked(2); + y *= T.CreateChecked(2); + } + + return new Complex(x, y); + } + + public static Complex Pow(Complex value, Complex power) + { + if (power == Zero) + { + return One; + } + + if (value == Zero) + { + return Zero; + } + + T valueReal = value.m_real; + T valueImaginary = value.m_imaginary; + T powerReal = power.m_real; + T powerImaginary = power.m_imaginary; + + T rho = Abs(value); + T theta = T.Atan2(valueImaginary, valueReal); + T newRho = powerReal * theta + powerImaginary * T.Log(rho); + + T t = T.Pow(rho, powerReal) * T.Exp(-powerImaginary * theta); + + return FromPolarCoordinates(t, newRho); + } + + public static Complex Pow(Complex value, T power) + { + return Pow(value, new Complex(power, T.Zero)); + } + + private static Complex Scale(Complex value, T factor) + { + T realResult = factor * value.m_real; + T imaginaryResult = factor * value.m_imaginary; + return new Complex(realResult, imaginaryResult); + } + + // + // Implicit Conversions To Complex + // + + public static implicit operator Complex(T value) + { + return new Complex(value, T.Zero); + } + + // + // IAdditiveIdentity + // + + /// + static Complex IAdditiveIdentity, Complex>.AdditiveIdentity => Zero; + + // + // IDecrementOperators + // + + /// + public static Complex operator --(Complex value) => value - One; + + // + // IIncrementOperators + // + + /// + public static Complex operator ++(Complex value) => value + One; + + // + // IMultiplicativeIdentity + // + + /// + static Complex IMultiplicativeIdentity, Complex>.MultiplicativeIdentity => One; + + // + // INumberBase + // + + /// + static int INumberBase>.Radix => T.Radix; + + /// + static Complex INumberBase>.Abs(Complex value) => Abs(value); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Complex CreateChecked(TOther value) + where TOther : INumberBase + { + Complex result; + + if (typeof(TOther) == typeof(Complex)) + { + result = (Complex)(object)value; + } + else if (!TryConvertFromCheckedCore(value, out result) && !TOther.TryConvertToChecked(value, out result)) + { + ThrowHelper.ThrowNotSupportedException(); + } + + return result; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Complex CreateSaturating(TOther value) + where TOther : INumberBase + { + Complex result; + + if (typeof(TOther) == typeof(Complex)) + { + result = (Complex)(object)value; + } + else if (!TryConvertFromSaturatingCore(value, out result) && !TOther.TryConvertToSaturating(value, out result)) + { + ThrowHelper.ThrowNotSupportedException(); + } + + return result; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Complex CreateTruncating(TOther value) + where TOther : INumberBase + { + Complex result; + + if (typeof(TOther) == typeof(Complex)) + { + result = (Complex)(object)value; + } + else if (!TryConvertFromTruncatingCore(value, out result) && !TOther.TryConvertToTruncating(value, out result)) + { + ThrowHelper.ThrowNotSupportedException(); + } + + return result; + } + + /// + static bool INumberBase>.IsCanonical(Complex value) => true; + + /// + public static bool IsComplexNumber(Complex value) => (value.m_real != T.Zero) && (value.m_imaginary != T.Zero); + + /// + public static bool IsEvenInteger(Complex value) => (value.m_imaginary == T.Zero) && T.IsEvenInteger(value.m_real); + + /// + public static bool IsImaginaryNumber(Complex value) => (value.m_real == T.Zero) && T.IsRealNumber(value.m_imaginary); + + /// + public static bool IsInteger(Complex value) => (value.m_imaginary == T.Zero) && T.IsInteger(value.m_real); + + /// + public static bool IsNegative(Complex value) + { + // since complex numbers do not have a well-defined concept of + // negative we report false if this value has an imaginary part + + return (value.m_imaginary == T.Zero) && T.IsNegative(value.m_real); + } + + /// + public static bool IsNegativeInfinity(Complex value) + { + // since complex numbers do not have a well-defined concept of + // negative we report false if this value has an imaginary part + + return (value.m_imaginary == T.Zero) && T.IsNegativeInfinity(value.m_real); + } + + /// + public static bool IsNormal(Complex value) + { + // much as IsFinite requires both parts to be finite, we require both + // parts to be "normal" (finite, non-zero, and non-subnormal) to be true + + return T.IsNormal(value.m_real) + && ((value.m_imaginary == T.Zero) || T.IsNormal(value.m_imaginary)); + } + + /// + public static bool IsOddInteger(Complex value) => (value.m_imaginary == T.Zero) && T.IsOddInteger(value.m_real); + + /// + public static bool IsPositive(Complex value) + { + // since complex numbers do not have a well-defined concept of + // negative we report false if this value has an imaginary part + + return (value.m_imaginary == T.Zero) && T.IsPositive(value.m_real); + } + + /// + public static bool IsPositiveInfinity(Complex value) + { + // since complex numbers do not have a well-defined concept of + // positive we report false if this value has an imaginary part + + return (value.m_imaginary == T.Zero) && T.IsPositiveInfinity(value.m_real); + } + + /// + public static bool IsRealNumber(Complex value) => (value.m_imaginary == T.Zero) && T.IsRealNumber(value.m_real); + + /// + public static bool IsSubnormal(Complex value) + { + // much as IsInfinite allows either part to be infinite, we allow either + // part to be "subnormal" (finite, non-zero, and non-normal) to be true + + return T.IsSubnormal(value.m_real) || T.IsSubnormal(value.m_imaginary); + } + + /// + static bool INumberBase>.IsZero(Complex value) => (value.m_real == T.Zero) && (value.m_imaginary == T.Zero); + + /// + public static Complex MaxMagnitude(Complex x, Complex y) + { + // complex numbers are not normally comparable, however every complex + // number has a real magnitude (absolute value) and so we can provide + // an implementation for MaxMagnitude + + // This matches the IEEE 754:2019 `maximumMagnitude` function + // + // It propagates NaN inputs back to the caller and + // otherwise returns the input with a larger magnitude. + // It treats +0 as larger than -0 as per the specification. + + T ax = Abs(x); + T ay = Abs(y); + + if ((ax > ay) || T.IsNaN(ax)) + { + return x; + } + + if (ax == ay) + { + // We have two equal magnitudes which means we have two of the following + // `+a + ib` + // `-a + ib` + // `+a - ib` + // `-a - ib` + // + // We want to treat `+a + ib` as greater than everything and `-a - ib` as + // lesser. For `-a + ib` and `+a - ib` its "ambiguous" which should be preferred + // so we will just preference `+a - ib` since that's the most correct choice + // in the face of something like `+a - i0.0` vs `-a + i0.0`. This is the "most + // correct" choice because both represent real numbers and `+a` is preferred + // over `-a`. + + if (T.IsNegative(y.m_real)) + { + if (T.IsNegative(y.m_imaginary)) + { + return x; + } + else + { + if (T.IsNegative(x.m_real)) + { + return y; + } + else + { + return x; + } + } + } + else if (T.IsNegative(y.m_imaginary)) + { + if (T.IsNegative(x.m_real)) + { + return y; + } + else + { + return x; + } + } + } + + return y; + } + + internal static Complex MaxMagnitudeNumber(Complex x, Complex y) + { + // complex numbers are not normally comparable, however every complex + // number has a real magnitude (absolute value) and so we can provide + // an implementation for MaxMagnitudeNumber + + // This matches the IEEE 754:2019 `maximumMagnitudeNumber` function + // + // It does not propagate NaN inputs back to the caller and + // otherwise returns the input with a larger magnitude. + // It treats +0 as larger than -0 as per the specification. + + T ax = Abs(x); + T ay = Abs(y); + + if ((ax > ay) || T.IsNaN(ay)) + { + return x; + } + + if (ax == ay) + { + if (T.IsNegative(y.m_real)) + { + if (T.IsNegative(y.m_imaginary)) + { + return x; + } + else + { + if (T.IsNegative(x.m_real)) + { + return y; + } + else + { + return x; + } + } + } + else if (T.IsNegative(y.m_imaginary)) + { + if (T.IsNegative(x.m_real)) + { + return y; + } + else + { + return x; + } + } + } + + return y; + } + + /// + static Complex INumberBase>.MaxMagnitudeNumber(Complex x, Complex y) + => MaxMagnitudeNumber(x, y); + + /// + public static Complex MinMagnitude(Complex x, Complex y) + { + // complex numbers are not normally comparable, however every complex + // number has a real magnitude (absolute value) and so we can provide + // an implementation for MinMagnitude + + // This matches the IEEE 754:2019 `minimumMagnitude` function + // + // It propagates NaN inputs back to the caller and + // otherwise returns the input with a smaller magnitude. + // It treats -0 as smaller than +0 as per the specification. + + T ax = Abs(x); + T ay = Abs(y); + + if ((ax < ay) || T.IsNaN(ax)) + { + return x; + } + + if (ax == ay) + { + if (T.IsNegative(y.m_real)) + { + if (T.IsNegative(y.m_imaginary)) + { + return y; + } + else + { + if (T.IsNegative(x.m_real)) + { + return x; + } + else + { + return y; + } + } + } + else if (T.IsNegative(y.m_imaginary)) + { + if (T.IsNegative(x.m_real)) + { + return x; + } + else + { + return y; + } + } + else + { + return x; + } + } + + return y; + } + + internal static Complex MinMagnitudeNumber(Complex x, Complex y) + { + // complex numbers are not normally comparable, however every complex + // number has a real magnitude (absolute value) and so we can provide + // an implementation for MinMagnitudeNumber + + // This matches the IEEE 754:2019 `minimumMagnitudeNumber` function + // + // It does not propagate NaN inputs back to the caller and + // otherwise returns the input with a smaller magnitude. + // It treats -0 as smaller than +0 as per the specification. + + T ax = Abs(x); + T ay = Abs(y); + + if ((ax < ay) || T.IsNaN(ay)) + { + return x; + } + + if (ax == ay) + { + if (T.IsNegative(y.m_real)) + { + if (T.IsNegative(y.m_imaginary)) + { + return y; + } + else + { + if (T.IsNegative(x.m_real)) + { + return x; + } + else + { + return y; + } + } + } + else if (T.IsNegative(y.m_imaginary)) + { + if (T.IsNegative(x.m_real)) + { + return x; + } + else + { + return y; + } + } + else + { + return x; + } + } + + return y; + } + + /// + static Complex INumberBase>.MinMagnitudeNumber(Complex x, Complex y) + => MinMagnitudeNumber(x, y); + + internal static Complex MultiplyAddEstimate(Complex left, Complex right, Complex addend) + { + // Multiplication: (a + bi)(c + di) = (ac - bd) + (bc + ad)i + // Addition: (a + bi) + (c + di) = (a + c) + (b + d)i + + T result_realpart = addend.m_real; + result_realpart = T.MultiplyAddEstimate(-left.m_imaginary, right.m_imaginary, result_realpart); + result_realpart = T.MultiplyAddEstimate(left.m_real, right.m_real, result_realpart); + + T result_imaginarypart = addend.m_imaginary; + result_imaginarypart = T.MultiplyAddEstimate(left.m_real, right.m_imaginary, result_imaginarypart); + result_imaginarypart = T.MultiplyAddEstimate(left.m_imaginary, right.m_real, result_imaginarypart); + + return new Complex(result_realpart, result_imaginarypart); + } + + /// + static Complex INumberBase>.MultiplyAddEstimate(Complex left, Complex right, Complex addend) + => MultiplyAddEstimate(left, right, addend); + + /// + public static Complex Parse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider) + { + if (!TryParse(s, style, provider, out Complex result)) + { + ThrowHelper.ThrowOverflowException(); + } + return result; + } + + /// + public static Complex Parse(ReadOnlySpan utf8Text, NumberStyles style, IFormatProvider? provider) + { + if (!TryParse(utf8Text, style, provider, out Complex result)) + { + ThrowHelper.ThrowOverflowException(); + } + return result; + } + + /// + public static Complex Parse(string s, NumberStyles style, IFormatProvider? provider) + { + ArgumentNullException.ThrowIfNull(s); + return Parse(s.AsSpan(), style, provider); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static bool INumberBase>.TryConvertFromChecked(TOther value, out Complex result) + { + return TryConvertFromCheckedCore(value, out result); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static bool INumberBase>.TryConvertFromSaturating(TOther value, out Complex result) + { + return TryConvertFromSaturatingCore(value, out result); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static bool INumberBase>.TryConvertFromTruncating(TOther value, out Complex result) + { + return TryConvertFromTruncatingCore(value, out result); + } + + internal static bool TryConvertFromCheckedCore(TOther value, out Complex result) + where TOther : INumberBase + { + if (typeof(TOther) == typeof(T)) + { + result = new Complex((T)(object)value, T.Zero); + return true; + } + + if (typeof(TOther) == typeof(Complex)) + { + Complex actualValue = (Complex)(object)value; + result = new Complex(T.CreateChecked(actualValue.Real), T.CreateChecked(actualValue.Imaginary)); + return true; + } + + if (T.TryConvertFromChecked(value, out T? realResult) && realResult is not null) + { + result = new Complex(realResult, T.Zero); + return true; + } + + if (TOther.TryConvertToChecked(value, out T? realResult2) && realResult2 is not null) + { + result = new Complex(realResult2, T.Zero); + return true; + } + + result = default; + return false; + } + + internal static bool TryConvertFromSaturatingCore(TOther value, out Complex result) + where TOther : INumberBase + { + if (typeof(TOther) == typeof(T)) + { + result = new Complex((T)(object)value, T.Zero); + return true; + } + + if (typeof(TOther) == typeof(Complex)) + { + Complex actualValue = (Complex)(object)value; + result = new Complex(T.CreateSaturating(actualValue.Real), T.CreateSaturating(actualValue.Imaginary)); + return true; + } + + if (T.TryConvertFromSaturating(value, out T? realResult) && realResult is not null) + { + result = new Complex(realResult, T.Zero); + return true; + } + + if (TOther.TryConvertToSaturating(value, out T? realResult2) && realResult2 is not null) + { + result = new Complex(realResult2, T.Zero); + return true; + } + + result = default; + return false; + } + + internal static bool TryConvertFromTruncatingCore(TOther value, out Complex result) + where TOther : INumberBase + { + if (typeof(TOther) == typeof(T)) + { + result = new Complex((T)(object)value, T.Zero); + return true; + } + + if (typeof(TOther) == typeof(Complex)) + { + Complex actualValue = (Complex)(object)value; + result = new Complex(T.CreateTruncating(actualValue.Real), T.CreateTruncating(actualValue.Imaginary)); + return true; + } + + if (T.TryConvertFromTruncating(value, out T? realResult) && realResult is not null) + { + result = new Complex(realResult, T.Zero); + return true; + } + + if (TOther.TryConvertToTruncating(value, out T? realResult2) && realResult2 is not null) + { + result = new Complex(realResult2, T.Zero); + return true; + } + + result = default; + return false; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static bool INumberBase>.TryConvertToChecked(Complex value, [MaybeNullWhen(false)] out TOther result) + { + if (typeof(TOther) == typeof(Complex)) + { + result = (TOther)(object)value; + return true; + } + + return TryConvertToCheckedCore(value, out result); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static bool INumberBase>.TryConvertToSaturating(Complex value, [MaybeNullWhen(false)] out TOther result) + { + if (typeof(TOther) == typeof(Complex)) + { + result = (TOther)(object)value; + return true; + } + + return TryConvertToSaturatingCore(value, out result); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static bool INumberBase>.TryConvertToTruncating(Complex value, [MaybeNullWhen(false)] out TOther result) + { + if (typeof(TOther) == typeof(Complex)) + { + result = (TOther)(object)value; + return true; + } + + return TryConvertToTruncatingCore(value, out result); + } + + internal static bool TryConvertToCheckedCore(Complex value, [MaybeNullWhen(false)] out TOther result) + where TOther : INumberBase + { + if (typeof(TOther) == typeof(T)) + { + // T is always IFloatingPointIeee754, so NaN is valid for the imaginary case. + result = (TOther)(object)((value.m_imaginary != T.Zero) ? T.NaN : value.m_real); + return true; + } + + if (typeof(TOther) == typeof(Complex)) + { + result = (TOther)(object)new Complex(double.CreateChecked(value.m_real), double.CreateChecked(value.m_imaginary)); + return true; + } + + if (typeof(TOther) == typeof(BigInteger)) + { + if (value.m_imaginary != T.Zero) + { + ThrowHelper.ThrowOverflowException(); + } + + BigInteger actualResult = checked((BigInteger)double.CreateChecked(value.m_real)); + result = (TOther)(object)actualResult; + return true; + } + + // A complex number with a non-zero imaginary part cannot be exactly represented as a real number. + // For floating-point types, we return NaN; for integer/decimal types, we throw. + if (value.m_imaginary != T.Zero) + { + if (!T.TryConvertToChecked(T.NaN, out result)) + { + ThrowHelper.ThrowOverflowException(); + } + return result is not null; + } + + if (T.TryConvertToChecked(value.m_real, out result)) + { + return true; + } + + return TOther.TryConvertFromChecked(value.m_real, out result); + } + + internal static bool TryConvertToSaturatingCore(Complex value, [MaybeNullWhen(false)] out TOther result) + where TOther : INumberBase + { + if (typeof(TOther) == typeof(T)) + { + result = (TOther)(object)value.m_real; + return true; + } + + if (typeof(TOther) == typeof(Complex)) + { + result = (TOther)(object)new Complex(double.CreateSaturating(value.m_real), double.CreateSaturating(value.m_imaginary)); + return true; + } + + if (typeof(TOther) == typeof(BigInteger)) + { + BigInteger actualResult = (BigInteger)double.CreateSaturating(value.m_real); + result = (TOther)(object)actualResult; + return true; + } + + // For saturating conversion, ignore the imaginary part and just saturate the real part + if (T.TryConvertToSaturating(value.m_real, out result)) + { + return true; + } + + return TOther.TryConvertFromSaturating(value.m_real, out result); + } + + internal static bool TryConvertToTruncatingCore(Complex value, [MaybeNullWhen(false)] out TOther result) + where TOther : INumberBase + { + if (typeof(TOther) == typeof(T)) + { + result = (TOther)(object)value.m_real; + return true; + } + + if (typeof(TOther) == typeof(Complex)) + { + result = (TOther)(object)new Complex(double.CreateTruncating(value.m_real), double.CreateTruncating(value.m_imaginary)); + return true; + } + + if (typeof(TOther) == typeof(BigInteger)) + { + BigInteger actualResult = (BigInteger)double.CreateTruncating(value.m_real); + result = (TOther)(object)actualResult; + return true; + } + + // For truncating conversion, ignore the imaginary part and just truncate the real part + if (T.TryConvertToTruncating(value.m_real, out result)) + { + return true; + } + + return TOther.TryConvertFromTruncating(value.m_real, out result); + } + + /// + public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out Complex result) + => TryParse(MemoryMarshal.Cast(s), style, provider, out result); + + /// + public static bool TryParse(ReadOnlySpan utf8Text, NumberStyles style, IFormatProvider? provider, out Complex result) + => TryParse(MemoryMarshal.Cast(utf8Text), style, provider, out result); + + private static bool TryParse(ReadOnlySpan text, NumberStyles style, IFormatProvider? provider, out Complex result) + where TChar : unmanaged, IUtfChar + { + ValidateParseStyleFloatingPoint(style); + + int openBracket = text.IndexOf(TChar.CastFrom('<')); + int semicolon = text.IndexOf(TChar.CastFrom(';')); + int closeBracket = text.IndexOf(TChar.CastFrom('>')); + + if ((text.Length < 5) || (openBracket == -1) || (semicolon == -1) || (closeBracket == -1) || (openBracket > semicolon) || (openBracket > closeBracket) || (semicolon > closeBracket)) + { + // We need at least 5 characters for `<0;0>` + // We also expect to find an open bracket, a semicolon, and a closing bracket in that order + + result = default; + return false; + } + + if ((openBracket != 0) && (((style & NumberStyles.AllowLeadingWhite) == 0) || !text.Slice(0, openBracket).IsWhiteSpace())) + { + result = default; + return false; + } + + ReadOnlySpan slice = text.Slice(openBracket + 1, semicolon - openBracket - 1); + + T? real, imaginary; + if ((typeof(TChar) == typeof(Utf8Char)) + ? !T.TryParse(Unsafe.BitCast, ReadOnlySpan>(slice), style, provider, out real) + : !T.TryParse(Unsafe.BitCast, ReadOnlySpan>(slice), style, provider, out real)) + { + result = default; + return false; + } + + if (Number.DecodeFromUtfChar(text[(semicolon + 1)..], out Rune rune, out int elemsConsumed) == OperationStatus.Done) + { + if (Rune.IsWhiteSpace(rune)) + { + // We allow a single whitespace after the semicolon regardless of style, this is so that + // the output of `ToString` can be correctly parsed by default and values will roundtrip. + semicolon += elemsConsumed; + } + } + + slice = text.Slice(semicolon + 1, closeBracket - semicolon - 1); + + if ((typeof(TChar) == typeof(Utf8Char)) + ? !T.TryParse(Unsafe.BitCast, ReadOnlySpan>(slice), style, provider, out imaginary) + : !T.TryParse(Unsafe.BitCast, ReadOnlySpan>(slice), style, provider, out imaginary)) + { + result = default; + return false; + } + + if ((closeBracket != (text.Length - 1)) && (((style & NumberStyles.AllowTrailingWhite) == 0) || !text.Slice(closeBracket).IsWhiteSpace())) + { + result = default; + return false; + } + + result = new Complex(real!, imaginary!); + return true; + } + + private static void ValidateParseStyleFloatingPoint(NumberStyles style) + { + // Check for undefined flags or hex number + if ((style & (Complex.InvalidNumberStyles | NumberStyles.AllowHexSpecifier)) != 0) + { + ThrowInvalid(style); + + static void ThrowInvalid(NumberStyles value) + { + if ((value & Complex.InvalidNumberStyles) != 0) + { + throw new ArgumentException(SR.Argument_InvalidNumberStyles, nameof(style)); + } + + throw new ArgumentException(SR.Arg_HexStyleNotSupported); + } + } + } + + /// + public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out Complex result) + { + if (s is null) + { + result = default; + return false; + } + return TryParse(s.AsSpan(), style, provider, out result); + } + + // + // IParsable + // + + /// + public static Complex Parse(string s, IFormatProvider? provider) => Parse(s, Complex.DefaultNumberStyle, provider); + + /// + public static bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, out Complex result) => TryParse(s, Complex.DefaultNumberStyle, provider, out result); + + // + // ISignedNumber + // + + /// + static Complex ISignedNumber>.NegativeOne => new(-T.One, T.Zero); + + // + // ISpanFormattable + // + + /// + public bool TryFormat(Span destination, out int charsWritten, [StringSyntax(StringSyntaxAttribute.NumericFormat)] ReadOnlySpan format = default, IFormatProvider? provider = null) => + TryFormat(MemoryMarshal.Cast(destination), out charsWritten, format, provider); + + /// + public bool TryFormat(Span utf8Destination, out int bytesWritten, [StringSyntax(StringSyntaxAttribute.NumericFormat)] ReadOnlySpan format = default, IFormatProvider? provider = null) => + TryFormat(MemoryMarshal.Cast(utf8Destination), out bytesWritten, format, provider); + + private bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) + where TChar : unmanaged, IUtfChar + { + Debug.Assert(typeof(TChar) == typeof(Utf8Char) || typeof(TChar) == typeof(Utf16Char)); + + // We have at least 6 more characters for: <0; 0> + if (destination.Length >= 6) + { + if ((typeof(TChar) == typeof(Utf8Char)) + ? m_real.TryFormat(Unsafe.BitCast, Span>(destination.Slice(1)), out int realChars, format, provider) + : m_real.TryFormat(Unsafe.BitCast, Span>(destination.Slice(1)), out realChars, format, provider)) + { + destination[0] = TChar.CastFrom('<'); + destination = destination.Slice(1 + realChars); // + 1 for < + + // We have at least 4 more characters for: ; 0> + if (destination.Length >= 4) + { + if ((typeof(TChar) == typeof(Utf8Char)) + ? m_imaginary.TryFormat(Unsafe.BitCast, Span>(destination.Slice(2)), out int imaginaryChars, format, provider) + : m_imaginary.TryFormat(Unsafe.BitCast, Span>(destination.Slice(2)), out imaginaryChars, format, provider)) + { + // We have 1 more character for: > + if ((uint)(2 + imaginaryChars) < (uint)destination.Length) + { + destination[0] = TChar.CastFrom(';'); + destination[1] = TChar.CastFrom(' '); + destination[2 + imaginaryChars] = TChar.CastFrom('>'); + + charsWritten = realChars + imaginaryChars + 4; + return true; + } + } + } + } + } + + charsWritten = 0; + return false; + } + + // + // ISpanParsable + // + + /// + public static Complex Parse(ReadOnlySpan s, IFormatProvider? provider) => Parse(s, Complex.DefaultNumberStyle, provider); + + /// + public static bool TryParse(ReadOnlySpan s, IFormatProvider? provider, out Complex result) => TryParse(s, Complex.DefaultNumberStyle, provider, out result); + + // + // IUnaryPlusOperators + // + + /// + public static Complex operator +(Complex value) => value; + + // + // IUtf8SpanParsable + // + + /// + public static Complex Parse(ReadOnlySpan utf8Text, IFormatProvider? provider) => Parse(utf8Text, Complex.DefaultNumberStyle, provider); + + /// + public static bool TryParse(ReadOnlySpan utf8Text, IFormatProvider? provider, out Complex result) => TryParse(utf8Text, Complex.DefaultNumberStyle, provider, out result); + } +} diff --git a/src/libraries/System.Runtime.Numerics/src/System/Numerics/Complex.cs b/src/libraries/System.Runtime.Numerics/src/System/Numerics/Complex.cs index 71e6d335271783..e530a38baa4660 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/Complex.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/Complex.cs @@ -24,9 +24,9 @@ public readonly struct Complex ISignedNumber, IUtf8SpanFormattable { - private const NumberStyles DefaultNumberStyle = NumberStyles.Float | NumberStyles.AllowThousands; + internal const NumberStyles DefaultNumberStyle = NumberStyles.Float | NumberStyles.AllowThousands; - private const NumberStyles InvalidNumberStyles = ~(NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite + internal const NumberStyles InvalidNumberStyles = ~(NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite | NumberStyles.AllowLeadingSign | NumberStyles.AllowTrailingSign | NumberStyles.AllowParentheses | NumberStyles.AllowDecimalPoint | NumberStyles.AllowThousands | NumberStyles.AllowExponent @@ -40,15 +40,6 @@ public readonly struct Complex private const double InverseOfLog10 = 0.43429448190325; // 1 / Log(10) - // This is the largest x for which (Hypot(x,x) + x) will not overflow. It is used for branching inside Sqrt. - private static readonly double s_sqrtRescaleThreshold = double.MaxValue / (Math.Sqrt(2.0) + 1.0); - - // This is the largest x for which 2 x^2 will not overflow. It is used for branching inside Asin and Acos. - private static readonly double s_asinOverflowThreshold = Math.Sqrt(double.MaxValue) / 2.0; - - // This value is used inside Asin and Acos. - private static readonly double s_log2 = Math.Log(2.0); - // Do not rename, these fields are needed for binary serialization private readonly double m_real; // Do not rename (binary serialization) private readonly double m_imaginary; // Do not rename (binary serialization) @@ -181,112 +172,32 @@ public static Complex Divide(double dividend, Complex divisor) public static Complex operator *(Complex left, double right) { - if (!double.IsFinite(left.m_real)) - { - if (!double.IsFinite(left.m_imaginary)) - { - return new Complex(double.NaN, double.NaN); - } - - return new Complex(left.m_real * right, double.NaN); - } - - if (!double.IsFinite(left.m_imaginary)) - { - return new Complex(double.NaN, left.m_imaginary * right); - } - - return new Complex(left.m_real * right, left.m_imaginary * right); + Complex result = new Complex(left.m_real, left.m_imaginary) * right; + return new Complex(result.Real, result.Imaginary); } public static Complex operator *(double left, Complex right) { - if (!double.IsFinite(right.m_real)) - { - if (!double.IsFinite(right.m_imaginary)) - { - return new Complex(double.NaN, double.NaN); - } - - return new Complex(left * right.m_real, double.NaN); - } - - if (!double.IsFinite(right.m_imaginary)) - { - return new Complex(double.NaN, left * right.m_imaginary); - } - - return new Complex(left * right.m_real, left * right.m_imaginary); + Complex result = left * new Complex(right.m_real, right.m_imaginary); + return new Complex(result.Real, result.Imaginary); } public static Complex operator /(Complex left, Complex right) { - // Division : Smith's formula. - double a = left.m_real; - double b = left.m_imaginary; - double c = right.m_real; - double d = right.m_imaginary; - - // Computing c * c + d * d will overflow even in cases where the actual result of the division does not overflow. - if (Math.Abs(d) < Math.Abs(c)) - { - double doc = d / c; - return new Complex((a + b * doc) / (c + d * doc), (b - a * doc) / (c + d * doc)); - } - else - { - double cod = c / d; - return new Complex((b + a * cod) / (d + c * cod), (-a + b * cod) / (d + c * cod)); - } + Complex result = new Complex(left.m_real, left.m_imaginary) / new Complex(right.m_real, right.m_imaginary); + return new Complex(result.Real, result.Imaginary); } public static Complex operator /(Complex left, double right) { - // IEEE prohibit optimizations which are value changing - // so we make sure that behaviour for the simplified version exactly match - // full version. - if (right == 0) - { - return new Complex(double.NaN, double.NaN); - } - - if (!double.IsFinite(left.m_real)) - { - if (!double.IsFinite(left.m_imaginary)) - { - return new Complex(double.NaN, double.NaN); - } - - return new Complex(left.m_real / right, double.NaN); - } - - if (!double.IsFinite(left.m_imaginary)) - { - return new Complex(double.NaN, left.m_imaginary / right); - } - - // Here the actual optimized version of code. - return new Complex(left.m_real / right, left.m_imaginary / right); + Complex result = new Complex(left.m_real, left.m_imaginary) / right; + return new Complex(result.Real, result.Imaginary); } public static Complex operator /(double left, Complex right) { - // Division : Smith's formula. - double a = left; - double c = right.m_real; - double d = right.m_imaginary; - - // Computing c * c + d * d will overflow even in cases where the actual result of the division does not overflow. - if (Math.Abs(d) < Math.Abs(c)) - { - double doc = d / c; - return new Complex(a / (c + d * doc), (-a * doc) / (c + d * doc)); - } - else - { - double cod = c / d; - return new Complex(a * cod / (d + c * cod), -a / (d + c * cod)); - } + Complex result = left / new Complex(right.m_real, right.m_imaginary); + return new Complex(result.Real, result.Imaginary); } public static double Abs(Complex value) @@ -294,32 +205,6 @@ public static double Abs(Complex value) return double.Hypot(value.m_real, value.m_imaginary); } - private static double Log1P(double x) - { - // Compute log(1 + x) without loss of accuracy when x is small. - - // Our only use case so far is for positive values, so this isn't coded to handle negative values. - Debug.Assert((x >= 0.0) || double.IsNaN(x)); - - double xp1 = 1.0 + x; - if (xp1 == 1.0) - { - return x; - } - else if (x < 0.75) - { - // This is accurate to within 5 ulp with any floating-point system that uses a guard digit, - // as proven in Theorem 4 of "What Every Computer Scientist Should Know About Floating-Point - // Arithmetic" (https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html) - return x * Math.Log(xp1) / (xp1 - 1.0); - } - else - { - return Math.Log(xp1); - } - - } - public static Complex Conjugate(Complex value) { // Conjugate of a Complex number: the conjugate of x+i*y is x-i*y @@ -365,16 +250,7 @@ public bool Equals(Complex value) public string ToString(IFormatProvider? provider) => ToString(null, provider); public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format, IFormatProvider? provider) - { - // $"<{m_real.ToString(format, provider)}; {m_imaginary.ToString(format, provider)}>"; - var handler = new DefaultInterpolatedStringHandler(4, 2, provider, stackalloc char[512]); - handler.AppendLiteral("<"); - handler.AppendFormatted(m_real, format); - handler.AppendLiteral("; "); - handler.AppendFormatted(m_imaginary, format); - handler.AppendLiteral(">"); - return handler.ToStringAndClear(); - } + => new Complex(m_real, m_imaginary).ToString(format, provider); public static Complex Sin(Complex value) { @@ -395,22 +271,8 @@ public static Complex Sinh(Complex value) public static Complex Asin(Complex value) { - Asin_Internal(Math.Abs(value.Real), Math.Abs(value.Imaginary), out double b, out double bPrime, out double v); - - double u; - if (bPrime < 0.0) - { - u = Math.Asin(b); - } - else - { - u = Math.Atan(bPrime); - } - - if (value.Real < 0.0) u = -u; - if (value.Imaginary < 0.0) v = -v; - - return new Complex(u, v); + Complex result = Complex.Asin(new Complex(value.m_real, value.m_imaginary)); + return new Complex(result.Real, result.Imaginary); } public static Complex Cos(Complex value) @@ -427,49 +289,14 @@ public static Complex Cosh(Complex value) public static Complex Acos(Complex value) { - Asin_Internal(Math.Abs(value.Real), Math.Abs(value.Imaginary), out double b, out double bPrime, out double v); - - double u; - if (bPrime < 0.0) - { - u = Math.Acos(b); - } - else - { - u = Math.Atan(1.0 / bPrime); - } - - if (value.Real < 0.0) u = Math.PI - u; - if (value.Imaginary > 0.0) v = -v; - - return new Complex(u, v); + Complex result = Complex.Acos(new Complex(value.m_real, value.m_imaginary)); + return new Complex(result.Real, result.Imaginary); } public static Complex Tan(Complex value) { - // tan z = sin z / cos z, but to avoid unnecessary repeated trig computations, use - // tan z = (sin(2x) + i sinh(2y)) / (cos(2x) + cosh(2y)) - // (see Abramowitz & Stegun 4.3.57 or derive by hand), and compute trig functions here. - - // This approach does not work for |y| > ~355, because sinh(2y) and cosh(2y) overflow, - // even though their ratio does not. In that case, divide through by cosh to get: - // tan z = (sin(2x) / cosh(2y) + i \tanh(2y)) / (1 + cos(2x) / cosh(2y)) - // which correctly computes the (tiny) real part and the (normal-sized) imaginary part. - - double x2 = 2.0 * value.m_real; - double y2 = 2.0 * value.m_imaginary; - (double sin, double cos) = Math.SinCos(x2); - double cosh = Math.Cosh(y2); - if (Math.Abs(value.m_imaginary) <= 4.0) - { - double D = cos + cosh; - return new Complex(sin / D, Math.Sinh(y2) / D); - } - else - { - double D = 1.0 + cos / cosh; - return new Complex(sin / cosh / D, Math.Tanh(y2) / D); - } + Complex result = Complex.Tan(new Complex(value.m_real, value.m_imaginary)); + return new Complex(result.Real, result.Imaginary); } public static Complex Tanh(Complex value) @@ -485,124 +312,6 @@ public static Complex Atan(Complex value) return (ImaginaryOne / two) * (Log(One - ImaginaryOne * value) - Log(One + ImaginaryOne * value)); } - private static void Asin_Internal(double x, double y, out double b, out double bPrime, out double v) - { - - // This method for the inverse complex sine (and cosine) is described in Hull, Fairgrieve, - // and Tang, "Implementing the Complex Arcsine and Arccosine Functions Using Exception Handling", - // ACM Transactions on Mathematical Software (1997) - // (https://www.researchgate.net/profile/Ping_Tang3/publication/220493330_Implementing_the_Complex_Arcsine_and_Arccosine_Functions_Using_Exception_Handling/links/55b244b208ae9289a085245d.pdf) - - // First, the basics: start with sin(w) = (e^{iw} - e^{-iw}) / (2i) = z. Here z is the input - // and w is the output. To solve for w, define t = e^{i w} and multiply through by t to - // get the quadratic equation t^2 - 2 i z t - 1 = 0. The solution is t = i z + sqrt(1 - z^2), so - // w = arcsin(z) = - i log( i z + sqrt(1 - z^2) ) - // Decompose z = x + i y, multiply out i z + sqrt(1 - z^2), use log(s) = |s| + i arg(s), and do a - // bunch of algebra to get the components of w = arcsin(z) = u + i v - // u = arcsin(beta) v = sign(y) log(alpha + sqrt(alpha^2 - 1)) - // where - // alpha = (rho + sigma) / 2 beta = (rho - sigma) / 2 - // rho = sqrt((x + 1)^2 + y^2) sigma = sqrt((x - 1)^2 + y^2) - // These formulas appear in DLMF section 4.23. (http://dlmf.nist.gov/4.23), along with the analogous - // arccos(w) = arccos(beta) - i sign(y) log(alpha + sqrt(alpha^2 - 1)) - // So alpha and beta together give us arcsin(w) and arccos(w). - - // As written, alpha is not susceptible to cancelation errors, but beta is. To avoid cancelation, note - // beta = (rho^2 - sigma^2) / (rho + sigma) / 2 = (2 x) / (rho + sigma) = x / alpha - // which is not subject to cancelation. Note alpha >= 1 and |beta| <= 1. - - // For alpha ~ 1, the argument of the log is near unity, so we compute (alpha - 1) instead, - // write the argument as 1 + (alpha - 1) + sqrt((alpha - 1)(alpha + 1)), and use the log1p function - // to compute the log without loss of accuracy. - // For beta ~ 1, arccos does not accurately resolve small angles, so we compute the tangent of the angle - // instead. - // Hull, Fairgrieve, and Tang derive formulas for (alpha - 1) and beta' = tan(u) that do not suffer - // from cancelation in these cases. - - // For simplicity, we assume all positive inputs and return all positive outputs. The caller should - // assign signs appropriate to the desired cut conventions. We return v directly since its magnitude - // is the same for both arcsin and arccos. Instead of u, we usually return beta and sometimes beta'. - // If beta' is not computed, it is set to -1; if it is computed, it should be used instead of beta - // to determine u. Compute u = arcsin(beta) or u = arctan(beta') for arcsin, u = arccos(beta) - // or arctan(1/beta') for arccos. - - Debug.Assert((x >= 0.0) || double.IsNaN(x)); - Debug.Assert((y >= 0.0) || double.IsNaN(y)); - - // For x or y large enough to overflow alpha^2, we can simplify our formulas and avoid overflow. - if ((x > s_asinOverflowThreshold) || (y > s_asinOverflowThreshold)) - { - b = -1.0; - bPrime = x / y; - - double small, big; - if (x < y) - { - small = x; - big = y; - } - else - { - small = y; - big = x; - } - double ratio = small / big; - v = s_log2 + Math.Log(big) + 0.5 * Log1P(ratio * ratio); - } - else - { - double r = double.Hypot((x + 1.0), y); - double s = double.Hypot((x - 1.0), y); - - double a = (r + s) * 0.5; - b = x / a; - - if (b > 0.75) - { - if (x <= 1.0) - { - double amx = (y * y / (r + (x + 1.0)) + (s + (1.0 - x))) * 0.5; - bPrime = x / Math.Sqrt((a + x) * amx); - } - else - { - // In this case, amx ~ y^2. Since we take the square root of amx, we should - // pull y out from under the square root so we don't lose its contribution - // when y^2 underflows. - double t = (1.0 / (r + (x + 1.0)) + 1.0 / (s + (x - 1.0))) * 0.5; - bPrime = x / y / Math.Sqrt((a + x) * t); - } - } - else - { - bPrime = -1.0; - } - - if (a < 1.5) - { - if (x < 1.0) - { - // This is another case where our expression is proportional to y^2 and - // we take its square root, so again we pull out a factor of y from - // under the square root. - double t = (1.0 / (r + (x + 1.0)) + 1.0 / (s + (1.0 - x))) * 0.5; - double am1 = y * y * t; - v = Log1P(am1 + y * Math.Sqrt(t * (a + 1.0))); - } - else - { - double am1 = (y * y / (r + (x + 1.0)) + (s + (x - 1.0))) * 0.5; - v = Log1P(am1 + Math.Sqrt(am1 * (a + 1.0))); - } - } - else - { - // Because of the test above, we can be sure that a * a will not overflow. - v = Math.Log(a + Math.Sqrt((a - 1.0) * (a + 1.0))); - } - } - } - public static bool IsFinite(Complex value) => double.IsFinite(value.m_real) && double.IsFinite(value.m_imaginary); public static bool IsInfinity(Complex value) => double.IsInfinity(value.m_real) || double.IsInfinity(value.m_imaginary); @@ -633,130 +342,14 @@ public static Complex Exp(Complex value) public static Complex Sqrt(Complex value) { - - // Handle NaN input cases according to IEEE 754 - if (double.IsNaN(value.m_real)) - { - if (double.IsInfinity(value.m_imaginary)) - { - return new Complex(double.PositiveInfinity, value.m_imaginary); - } - return new Complex(double.NaN, double.NaN); - } - if (double.IsNaN(value.m_imaginary)) - { - if (double.IsPositiveInfinity(value.m_real)) - { - return new Complex(double.NaN, double.PositiveInfinity); - } - if (double.IsNegativeInfinity(value.m_real)) - { - return new Complex(double.PositiveInfinity, double.NaN); - } - return new Complex(double.NaN, double.NaN); - } - - if (value.m_imaginary == 0.0) - { - // Handle the trivial case quickly. - if (value.m_real < 0.0) - { - return new Complex(0.0, Math.Sqrt(-value.m_real)); - } - - return new Complex(Math.Sqrt(value.m_real), 0.0); - } - - // One way to compute Sqrt(z) is just to call Pow(z, 0.5), which coverts to polar coordinates - // (sqrt + atan), halves the phase, and reconverts to cartesian coordinates (cos + sin). - // Not only is this more expensive than necessary, it also fails to preserve certain expected - // symmetries, such as that the square root of a pure negative is a pure imaginary, and that the - // square root of a pure imaginary has exactly equal real and imaginary parts. This all goes - // back to the fact that Math.PI is not stored with infinite precision, so taking half of Math.PI - // does not land us on an argument with cosine exactly equal to zero. - - // To find a fast and symmetry-respecting formula for complex square root, - // note x + i y = \sqrt{a + i b} implies x^2 + 2 i x y - y^2 = a + i b, - // so x^2 - y^2 = a and 2 x y = b. Cross-substitute and use the quadratic formula to obtain - // x = \sqrt{\frac{\sqrt{a^2 + b^2} + a}{2}} y = \pm \sqrt{\frac{\sqrt{a^2 + b^2} - a}{2}} - // There is just one complication: depending on the sign on a, either x or y suffers from - // cancelation when |b| << |a|. We can get around this by noting that our formulas imply - // x^2 y^2 = b^2 / 4, so |x| |y| = |b| / 2. So after computing the one that doesn't suffer - // from cancelation, we can compute the other with just a division. This is basically just - // the right way to evaluate the quadratic formula without cancelation. - - // All this reduces our total cost to two sqrts and a few flops, and it respects the desired - // symmetries. Much better than atan + cos + sin! - - // The signs are a matter of choice of branch cut, which is traditionally taken so x > 0 and sign(y) = sign(b). - - // If the components are too large, Hypot will overflow, even though the subsequent sqrt would - // make the result representable. To avoid this, we re-scale (by exact powers of 2 for accuracy) - // when we encounter very large components to avoid intermediate infinities. - bool rescale = false; - double realCopy = value.m_real; - double imaginaryCopy = value.m_imaginary; - if ((Math.Abs(realCopy) >= s_sqrtRescaleThreshold) || (Math.Abs(imaginaryCopy) >= s_sqrtRescaleThreshold)) - { - if (double.IsInfinity(value.m_imaginary)) - { - // We need to handle infinite imaginary parts specially because otherwise - // our formulas below produce inf/inf = NaN. - return (new Complex(double.PositiveInfinity, imaginaryCopy)); - } - - realCopy *= 0.25; - imaginaryCopy *= 0.25; - rescale = true; - } - - // This is the core of the algorithm. Everything else is special case handling. - double x, y; - if (realCopy >= 0.0) - { - x = Math.Sqrt((double.Hypot(realCopy, imaginaryCopy) + realCopy) * 0.5); - y = imaginaryCopy / (2.0 * x); - } - else - { - y = Math.Sqrt((double.Hypot(realCopy, imaginaryCopy) - realCopy) * 0.5); - if (imaginaryCopy < 0.0) y = -y; - x = imaginaryCopy / (2.0 * y); - } - - if (rescale) - { - x *= 2.0; - y *= 2.0; - } - - return new Complex(x, y); + Complex result = Complex.Sqrt(new Complex(value.m_real, value.m_imaginary)); + return new Complex(result.Real, result.Imaginary); } public static Complex Pow(Complex value, Complex power) { - if (power == Zero) - { - return One; - } - - if (value == Zero) - { - return Zero; - } - - double valueReal = value.m_real; - double valueImaginary = value.m_imaginary; - double powerReal = power.m_real; - double powerImaginary = power.m_imaginary; - - double rho = Abs(value); - double theta = Math.Atan2(valueImaginary, valueReal); - double newRho = powerReal * theta + powerImaginary * Math.Log(rho); - - double t = Math.Pow(rho, powerReal) * Math.Exp(-powerImaginary * theta); - - return FromPolarCoordinates(t, newRho); + Complex result = Complex.Pow(new Complex(value.m_real, value.m_imaginary), new Complex(power.m_real, power.m_imaginary)); + return new Complex(result.Real, result.Imaginary); } public static Complex Pow(Complex value, double power) @@ -1084,1048 +677,143 @@ public static bool IsSubnormal(Complex value) /// public static Complex MaxMagnitude(Complex x, Complex y) { - // complex numbers are not normally comparable, however every complex - // number has a real magnitude (absolute value) and so we can provide - // an implementation for MaxMagnitude - - // This matches the IEEE 754:2019 `maximumMagnitude` function - // - // It propagates NaN inputs back to the caller and - // otherwise returns the input with a larger magnitude. - // It treats +0 as larger than -0 as per the specification. - - double ax = Abs(x); - double ay = Abs(y); - - if ((ax > ay) || double.IsNaN(ax)) - { - return x; - } - - if (ax == ay) - { - // We have two equal magnitudes which means we have two of the following - // `+a + ib` - // `-a + ib` - // `+a - ib` - // `-a - ib` - // - // We want to treat `+a + ib` as greater than everything and `-a - ib` as - // lesser. For `-a + ib` and `+a - ib` its "ambiguous" which should be preferred - // so we will just preference `+a - ib` since that's the most correct choice - // in the face of something like `+a - i0.0` vs `-a + i0.0`. This is the "most - // correct" choice because both represent real numbers and `+a` is preferred - // over `-a`. - - if (double.IsNegative(y.m_real)) - { - if (double.IsNegative(y.m_imaginary)) - { - // when `y` is `-a - ib` we always prefer `x` (its either the same as - // `x` or some part of `x` is positive). - - return x; - } - else - { - if (double.IsNegative(x.m_real)) - { - // when `y` is `-a + ib` and `x` is `-a + ib` or `-a - ib` then - // we either have same value or both parts of `x` are negative - // and we want to prefer `y`. - - return y; - } - else - { - // when `y` is `-a + ib` and `x` is `+a + ib` or `+a - ib` then - // we want to prefer `x` because either both parts are positive - // or we want to prefer `+a - ib` due to how it handles when `x` - // represents a real number. - - return x; - } - } - } - else if (double.IsNegative(y.m_imaginary)) - { - if (double.IsNegative(x.m_real)) - { - // when `y` is `+a - ib` and `x` is `-a + ib` or `-a - ib` then - // we either both parts of `x` are negative or we want to prefer - // `+a - ib` due to how it handles when `y` represents a real number. - - return y; - } - else - { - // when `y` is `+a - ib` and `x` is `+a + ib` or `+a - ib` then - // we want to prefer `x` because either both parts are positive - // or they represent the same value. - - return x; - } - } - } - - return y; + Complex result = Complex.MaxMagnitude(new Complex(x.m_real, x.m_imaginary), new Complex(y.m_real, y.m_imaginary)); + return new Complex(result.Real, result.Imaginary); } /// static Complex INumberBase.MaxMagnitudeNumber(Complex x, Complex y) { - // complex numbers are not normally comparable, however every complex - // number has a real magnitude (absolute value) and so we can provide - // an implementation for MaxMagnitudeNumber + Complex result = Complex.MaxMagnitudeNumber(new Complex(x.m_real, x.m_imaginary), new Complex(y.m_real, y.m_imaginary)); + return new Complex(result.Real, result.Imaginary); + } + + /// + public static Complex MinMagnitude(Complex x, Complex y) + { + Complex result = Complex.MinMagnitude(new Complex(x.m_real, x.m_imaginary), new Complex(y.m_real, y.m_imaginary)); + return new Complex(result.Real, result.Imaginary); + } - // This matches the IEEE 754:2019 `maximumMagnitudeNumber` function - // - // It does not propagate NaN inputs back to the caller and - // otherwise returns the input with a larger magnitude. - // It treats +0 as larger than -0 as per the specification. + /// + static Complex INumberBase.MinMagnitudeNumber(Complex x, Complex y) + { + Complex result = Complex.MinMagnitudeNumber(new Complex(x.m_real, x.m_imaginary), new Complex(y.m_real, y.m_imaginary)); + return new Complex(result.Real, result.Imaginary); + } - double ax = Abs(x); - double ay = Abs(y); + /// + static Complex INumberBase.MultiplyAddEstimate(Complex left, Complex right, Complex addend) + { + Complex result = Complex.MultiplyAddEstimate(new Complex(left.m_real, left.m_imaginary), new Complex(right.m_real, right.m_imaginary), new Complex(addend.m_real, addend.m_imaginary)); + return new Complex(result.Real, result.Imaginary); + } - if ((ax > ay) || double.IsNaN(ay)) + /// + public static Complex Parse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider) + { + if (!TryParse(s, style, provider, out Complex result)) { - return x; + ThrowHelper.ThrowOverflowException(); } + return result; + } - if (ax == ay) + /// + public static Complex Parse(ReadOnlySpan utf8Text, NumberStyles style, IFormatProvider? provider) + { + if (!TryParse(utf8Text, style, provider, out Complex result)) { - // We have two equal magnitudes which means we have two of the following - // `+a + ib` - // `-a + ib` - // `+a - ib` - // `-a - ib` - // - // We want to treat `+a + ib` as greater than everything and `-a - ib` as - // lesser. For `-a + ib` and `+a - ib` its "ambiguous" which should be preferred - // so we will just preference `+a - ib` since that's the most correct choice - // in the face of something like `+a - i0.0` vs `-a + i0.0`. This is the "most - // correct" choice because both represent real numbers and `+a` is preferred - // over `-a`. - - if (double.IsNegative(y.m_real)) - { - if (double.IsNegative(y.m_imaginary)) - { - // when `y` is `-a - ib` we always prefer `x` (its either the same as - // `x` or some part of `x` is positive). - - return x; - } - else - { - if (double.IsNegative(x.m_real)) - { - // when `y` is `-a + ib` and `x` is `-a + ib` or `-a - ib` then - // we either have same value or both parts of `x` are negative - // and we want to prefer `y`. - - return y; - } - else - { - // when `y` is `-a + ib` and `x` is `+a + ib` or `+a - ib` then - // we want to prefer `x` because either both parts are positive - // or we want to prefer `+a - ib` due to how it handles when `x` - // represents a real number. - - return x; - } - } - } - else if (double.IsNegative(y.m_imaginary)) - { - if (double.IsNegative(x.m_real)) - { - // when `y` is `+a - ib` and `x` is `-a + ib` or `-a - ib` then - // we either both parts of `x` are negative or we want to prefer - // `+a - ib` due to how it handles when `y` represents a real number. - - return y; - } - else - { - // when `y` is `+a - ib` and `x` is `+a + ib` or `+a - ib` then - // we want to prefer `x` because either both parts are positive - // or they represent the same value. - - return x; - } - } + ThrowHelper.ThrowOverflowException(); } + return result; + } - return y; + /// + public static Complex Parse(string s, NumberStyles style, IFormatProvider? provider) + { + ArgumentNullException.ThrowIfNull(s); + return Parse(s.AsSpan(), style, provider); } - /// - public static Complex MinMagnitude(Complex x, Complex y) + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static bool INumberBase.TryConvertFromChecked(TOther value, out Complex result) { - // complex numbers are not normally comparable, however every complex - // number has a real magnitude (absolute value) and so we can provide - // an implementation for MaxMagnitude + return TryConvertFrom(value, out result); + } - // This matches the IEEE 754:2019 `minimumMagnitude` function - // - // It propagates NaN inputs back to the caller and - // otherwise returns the input with a smaller magnitude. - // It treats -0 as smaller than +0 as per the specification. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static bool INumberBase.TryConvertFromSaturating(TOther value, out Complex result) + { + return TryConvertFrom(value, out result); + } - double ax = Abs(x); - double ay = Abs(y); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static bool INumberBase.TryConvertFromTruncating(TOther value, out Complex result) + { + return TryConvertFrom(value, out result); + } - if ((ax < ay) || double.IsNaN(ax)) + private static bool TryConvertFrom(TOther value, out Complex result) + where TOther : INumberBase + { + if (typeof(TOther) == typeof(Complex)) { - return x; + Complex actualValue = (Complex)(object)value; + result = new Complex(actualValue.Real, actualValue.Imaginary); + return true; } - if (ax == ay) + if (Complex.TryConvertFromCheckedCore(value, out Complex intermediate)) { - // We have two equal magnitudes which means we have two of the following - // `+a + ib` - // `-a + ib` - // `+a - ib` - // `-a - ib` - // - // We want to treat `+a + ib` as greater than everything and `-a - ib` as - // lesser. For `-a + ib` and `+a - ib` its "ambiguous" which should be preferred - // so we will just preference `-a + ib` since that's the most correct choice - // in the face of something like `+a - i0.0` vs `-a + i0.0`. This is the "most - // correct" choice because both represent real numbers and `-a` is preferred - // over `+a`. - - if (double.IsNegative(y.m_real)) - { - if (double.IsNegative(y.m_imaginary)) - { - // when `y` is `-a - ib` we always prefer `y` as both parts are negative - return y; - } - else - { - if (double.IsNegative(x.m_real)) - { - // when `y` is `-a + ib` and `x` is `-a + ib` or `-a - ib` then - // we either have same value or both parts of `x` are negative - // and we want to prefer it. - - return x; - } - else - { - // when `y` is `-a + ib` and `x` is `+a + ib` or `+a - ib` then - // we want to prefer `y` because either both parts of 'x' are positive - // or we want to prefer `-a - ib` due to how it handles when `y` - // represents a real number. - - return y; - } - } - } - else if (double.IsNegative(y.m_imaginary)) - { - if (double.IsNegative(x.m_real)) - { - // when `y` is `+a - ib` and `x` is `-a + ib` or `-a - ib` then - // either both parts of `x` are negative or we want to prefer - // `-a - ib` due to how it handles when `x` represents a real number. - - return x; - } - else - { - // when `y` is `+a - ib` and `x` is `+a + ib` or `+a - ib` then - // we want to prefer `y` because either both parts of x are positive - // or they represent the same value. - - return y; - } - } - else - { - return x; - } - } - - return y; - } - - /// - static Complex INumberBase.MinMagnitudeNumber(Complex x, Complex y) - { - // complex numbers are not normally comparable, however every complex - // number has a real magnitude (absolute value) and so we can provide - // an implementation for MinMagnitudeNumber - - // This matches the IEEE 754:2019 `minimumMagnitudeNumber` function - // - // It does not propagate NaN inputs back to the caller and - // otherwise returns the input with a smaller magnitude. - // It treats -0 as smaller than +0 as per the specification. - - double ax = Abs(x); - double ay = Abs(y); - - if ((ax < ay) || double.IsNaN(ay)) - { - return x; - } - - if (ax == ay) - { - // We have two equal magnitudes which means we have two of the following - // `+a + ib` - // `-a + ib` - // `+a - ib` - // `-a - ib` - // - // We want to treat `+a + ib` as greater than everything and `-a - ib` as - // lesser. For `-a + ib` and `+a - ib` its "ambiguous" which should be preferred - // so we will just preference `-a + ib` since that's the most correct choice - // in the face of something like `+a - i0.0` vs `-a + i0.0`. This is the "most - // correct" choice because both represent real numbers and `-a` is preferred - // over `+a`. - - if (double.IsNegative(y.m_real)) - { - if (double.IsNegative(y.m_imaginary)) - { - // when `y` is `-a - ib` we always prefer `y` as both parts are negative - return y; - } - else - { - if (double.IsNegative(x.m_real)) - { - // when `y` is `-a + ib` and `x` is `-a + ib` or `-a - ib` then - // we either have same value or both parts of `x` are negative - // and we want to prefer it. - - return x; - } - else - { - // when `y` is `-a + ib` and `x` is `+a + ib` or `+a - ib` then - // we want to prefer `y` because either both parts of 'x' are positive - // or we want to prefer `-a - ib` due to how it handles when `y` - // represents a real number. - - return y; - } - } - } - else if (double.IsNegative(y.m_imaginary)) - { - if (double.IsNegative(x.m_real)) - { - // when `y` is `+a - ib` and `x` is `-a + ib` or `-a - ib` then - // either both parts of `x` are negative or we want to prefer - // `-a - ib` due to how it handles when `x` represents a real number. - - return x; - } - else - { - // when `y` is `+a - ib` and `x` is `+a + ib` or `+a - ib` then - // we want to prefer `y` because either both parts of x are positive - // or they represent the same value. - - return y; - } - } - else - { - return x; - } - } - - return y; - } - - /// - static Complex INumberBase.MultiplyAddEstimate(Complex left, Complex right, Complex addend) - { - // Multiplication: (a + bi)(c + di) = (ac - bd) + (bc + ad)i - // Addition: (a + bi) + (c + di) = (a + c) + (b + d)i - - double result_realpart = addend.m_real; - result_realpart = double.MultiplyAddEstimate(-left.m_imaginary, right.m_imaginary, result_realpart); - result_realpart = double.MultiplyAddEstimate(left.m_real, right.m_real, result_realpart); - - double result_imaginarypart = addend.m_imaginary; - result_imaginarypart = double.MultiplyAddEstimate(left.m_real, right.m_imaginary, result_imaginarypart); - result_imaginarypart = double.MultiplyAddEstimate(left.m_imaginary, right.m_real, result_imaginarypart); - - return new Complex(result_realpart, result_imaginarypart); - } - - /// - public static Complex Parse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider) - { - if (!TryParse(s, style, provider, out Complex result)) - { - ThrowHelper.ThrowOverflowException(); - } - return result; - } - - /// - public static Complex Parse(ReadOnlySpan utf8Text, NumberStyles style, IFormatProvider? provider) - { - if (!TryParse(utf8Text, style, provider, out Complex result)) - { - ThrowHelper.ThrowOverflowException(); - } - return result; - } - - /// - public static Complex Parse(string s, NumberStyles style, IFormatProvider? provider) - { - ArgumentNullException.ThrowIfNull(s); - return Parse(s.AsSpan(), style, provider); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertFromChecked(TOther value, out Complex result) - { - return TryConvertFrom(value, out result); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertFromSaturating(TOther value, out Complex result) - { - return TryConvertFrom(value, out result); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - static bool INumberBase.TryConvertFromTruncating(TOther value, out Complex result) - { - return TryConvertFrom(value, out result); - } - - private static bool TryConvertFrom(TOther value, out Complex result) - where TOther : INumberBase - { - // We don't want to defer to `double.Create*(value)` because some type might have its own - // `TOther.ConvertTo*(value, out Complex result)` handling that would end up bypassed. - - if (typeof(TOther) == typeof(byte)) - { - byte actualValue = (byte)(object)value; - result = actualValue; - return true; - } - else if (typeof(TOther) == typeof(char)) - { - char actualValue = (char)(object)value; - result = actualValue; + result = new Complex(intermediate.Real, intermediate.Imaginary); return true; } - else if (typeof(TOther) == typeof(decimal)) - { - decimal actualValue = (decimal)(object)value; - result = (Complex)actualValue; - return true; - } - else if (typeof(TOther) == typeof(double)) - { - double actualValue = (double)(object)value; - result = actualValue; - return true; - } - else if (typeof(TOther) == typeof(Half)) - { - Half actualValue = (Half)(object)value; - result = actualValue; - return true; - } - else if (typeof(TOther) == typeof(BFloat16)) - { - BFloat16 actualValue = (BFloat16)(object)value; - result = actualValue; - return true; - } - else if (typeof(TOther) == typeof(short)) - { - short actualValue = (short)(object)value; - result = actualValue; - return true; - } - else if (typeof(TOther) == typeof(int)) - { - int actualValue = (int)(object)value; - result = actualValue; - return true; - } - else if (typeof(TOther) == typeof(long)) - { - long actualValue = (long)(object)value; - result = actualValue; - return true; - } - else if (typeof(TOther) == typeof(Int128)) - { - Int128 actualValue = (Int128)(object)value; - result = (Complex)actualValue; - return true; - } - else if (typeof(TOther) == typeof(nint)) - { - nint actualValue = (nint)(object)value; - result = actualValue; - return true; - } - else if (typeof(TOther) == typeof(sbyte)) - { - sbyte actualValue = (sbyte)(object)value; - result = actualValue; - return true; - } - else if (typeof(TOther) == typeof(float)) - { - float actualValue = (float)(object)value; - result = actualValue; - return true; - } - else if (typeof(TOther) == typeof(ushort)) - { - ushort actualValue = (ushort)(object)value; - result = actualValue; - return true; - } - else if (typeof(TOther) == typeof(uint)) - { - uint actualValue = (uint)(object)value; - result = actualValue; - return true; - } - else if (typeof(TOther) == typeof(ulong)) - { - ulong actualValue = (ulong)(object)value; - result = actualValue; - return true; - } - else if (typeof(TOther) == typeof(UInt128)) - { - UInt128 actualValue = (UInt128)(object)value; - result = (Complex)actualValue; - return true; - } - else if (typeof(TOther) == typeof(nuint)) - { - nuint actualValue = (nuint)(object)value; - result = actualValue; - return true; - } - else - { - result = default; - return false; - } + + result = default; + return false; } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] static bool INumberBase.TryConvertToChecked(Complex value, [MaybeNullWhen(false)] out TOther result) { - // Complex numbers with an imaginary part can't be represented as a "real number" - // so we'll throw an OverflowException for this scenario for integer types and - // for decimal. However, we will convert it to NaN for the floating-point types, - // since that's what Sqrt(-1) (which is `new Complex(0, 1)`) results in. - - if (typeof(TOther) == typeof(byte)) + if (typeof(TOther) == typeof(Complex)) { - if (value.m_imaginary != 0) - { - ThrowHelper.ThrowOverflowException(); - } - - byte actualResult = checked((byte)value.m_real); - result = (TOther)(object)actualResult; + result = (TOther)(object)new Complex(value.m_real, value.m_imaginary); return true; } - else if (typeof(TOther) == typeof(char)) - { - if (value.m_imaginary != 0) - { - ThrowHelper.ThrowOverflowException(); - } - char actualResult = checked((char)value.m_real); - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(decimal)) - { - if (value.m_imaginary != 0) - { - ThrowHelper.ThrowOverflowException(); - } - - decimal actualResult = checked((decimal)value.m_real); - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(double)) - { - double actualResult = (value.m_imaginary != 0) ? double.NaN : value.m_real; - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(Half)) - { - Half actualResult = (value.m_imaginary != 0) ? Half.NaN : (Half)value.m_real; - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(BFloat16)) - { - BFloat16 actualResult = (value.m_imaginary != 0) ? BFloat16.NaN : (BFloat16)value.m_real; - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(short)) - { - if (value.m_imaginary != 0) - { - ThrowHelper.ThrowOverflowException(); - } - - short actualResult = checked((short)value.m_real); - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(int)) - { - if (value.m_imaginary != 0) - { - ThrowHelper.ThrowOverflowException(); - } - - int actualResult = checked((int)value.m_real); - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(long)) - { - if (value.m_imaginary != 0) - { - ThrowHelper.ThrowOverflowException(); - } - - long actualResult = checked((long)value.m_real); - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(Int128)) - { - if (value.m_imaginary != 0) - { - ThrowHelper.ThrowOverflowException(); - } - - Int128 actualResult = checked((Int128)value.m_real); - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(nint)) - { - if (value.m_imaginary != 0) - { - ThrowHelper.ThrowOverflowException(); - } - - nint actualResult = checked((nint)value.m_real); - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(BigInteger)) - { - if (value.m_imaginary != 0) - { - ThrowHelper.ThrowOverflowException(); - } - - BigInteger actualResult = checked((BigInteger)value.m_real); - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(sbyte)) - { - if (value.m_imaginary != 0) - { - ThrowHelper.ThrowOverflowException(); - } - - sbyte actualResult = checked((sbyte)value.m_real); - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(float)) - { - float actualResult = (value.m_imaginary != 0) ? float.NaN : (float)value.m_real; - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(ushort)) - { - if (value.m_imaginary != 0) - { - ThrowHelper.ThrowOverflowException(); - } - - ushort actualResult = checked((ushort)value.m_real); - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(uint)) - { - if (value.m_imaginary != 0) - { - ThrowHelper.ThrowOverflowException(); - } - - uint actualResult = checked((uint)value.m_real); - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(ulong)) - { - if (value.m_imaginary != 0) - { - ThrowHelper.ThrowOverflowException(); - } - - ulong actualResult = checked((ulong)value.m_real); - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(UInt128)) - { - if (value.m_imaginary != 0) - { - ThrowHelper.ThrowOverflowException(); - } - - UInt128 actualResult = checked((UInt128)value.m_real); - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(nuint)) - { - if (value.m_imaginary != 0) - { - ThrowHelper.ThrowOverflowException(); - } - - nuint actualResult = checked((nuint)value.m_real); - result = (TOther)(object)actualResult; - return true; - } - else - { - result = default; - return false; - } + return Complex.TryConvertToCheckedCore(new Complex(value.m_real, value.m_imaginary), out result); } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] static bool INumberBase.TryConvertToSaturating(Complex value, [MaybeNullWhen(false)] out TOther result) { - // Complex numbers with an imaginary part can't be represented as a "real number" - // and there isn't really a well-defined way to "saturate" to just a real value. - // - // The two potential options are that we either treat complex numbers with a non- - // zero imaginary part as NaN and then convert that to 0 -or- we ignore the imaginary - // part and only consider the real part. - // - // We use the latter below since that is "more useful" given an unknown number type. - // Users who want 0 instead can always check `IsComplexNumber` and special-case the - // handling. - - if (typeof(TOther) == typeof(byte)) + if (typeof(TOther) == typeof(Complex)) { - byte actualResult = (value.m_real >= byte.MaxValue) ? byte.MaxValue : - (value.m_real <= byte.MinValue) ? byte.MinValue : (byte)value.m_real; - result = (TOther)(object)actualResult; + result = (TOther)(object)new Complex(value.m_real, value.m_imaginary); return true; } - else if (typeof(TOther) == typeof(char)) - { - char actualResult = (value.m_real >= char.MaxValue) ? char.MaxValue : - (value.m_real <= char.MinValue) ? char.MinValue : (char)value.m_real; - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(decimal)) - { - decimal actualResult = (value.m_real >= (double)decimal.MaxValue) ? decimal.MaxValue : - (value.m_real <= (double)decimal.MinValue) ? decimal.MinValue : (decimal)value.m_real; - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(double)) - { - double actualResult = value.m_real; - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(Half)) - { - Half actualResult = (Half)value.m_real; - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(BFloat16)) - { - BFloat16 actualResult = (BFloat16)value.m_real; - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(short)) - { - short actualResult = (value.m_real >= short.MaxValue) ? short.MaxValue : - (value.m_real <= short.MinValue) ? short.MinValue : (short)value.m_real; - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(int)) - { - int actualResult = (value.m_real >= int.MaxValue) ? int.MaxValue : - (value.m_real <= int.MinValue) ? int.MinValue : (int)value.m_real; - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(long)) - { - long actualResult = (value.m_real >= long.MaxValue) ? long.MaxValue : - (value.m_real <= long.MinValue) ? long.MinValue : (long)value.m_real; - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(Int128)) - { - Int128 actualResult = (value.m_real >= +170141183460469231731687303715884105727.0) ? Int128.MaxValue : - (value.m_real <= -170141183460469231731687303715884105728.0) ? Int128.MinValue : (Int128)value.m_real; - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(nint)) - { - nint actualResult = (value.m_real >= nint.MaxValue) ? nint.MaxValue : - (value.m_real <= nint.MinValue) ? nint.MinValue : (nint)value.m_real; - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(BigInteger)) - { - BigInteger actualResult = (BigInteger)value.m_real; - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(sbyte)) - { - sbyte actualResult = (value.m_real >= sbyte.MaxValue) ? sbyte.MaxValue : - (value.m_real <= sbyte.MinValue) ? sbyte.MinValue : (sbyte)value.m_real; - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(float)) - { - float actualResult = (float)value.m_real; - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(ushort)) - { - ushort actualResult = (value.m_real >= ushort.MaxValue) ? ushort.MaxValue : - (value.m_real <= ushort.MinValue) ? ushort.MinValue : (ushort)value.m_real; - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(uint)) - { - uint actualResult = (value.m_real >= uint.MaxValue) ? uint.MaxValue : - (value.m_real <= uint.MinValue) ? uint.MinValue : (uint)value.m_real; - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(ulong)) - { - ulong actualResult = (value.m_real >= ulong.MaxValue) ? ulong.MaxValue : - (value.m_real <= ulong.MinValue) ? ulong.MinValue : (ulong)value.m_real; - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(UInt128)) - { - UInt128 actualResult = (value.m_real >= 340282366920938463463374607431768211455.0) ? UInt128.MaxValue : - (value.m_real <= 0.0) ? UInt128.MinValue : (UInt128)value.m_real; - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(nuint)) - { - nuint actualResult = (value.m_real >= nuint.MaxValue) ? nuint.MaxValue : - (value.m_real <= nuint.MinValue) ? nuint.MinValue : (nuint)value.m_real; - result = (TOther)(object)actualResult; - return true; - } - else - { - result = default; - return false; - } + + return Complex.TryConvertToSaturatingCore(new Complex(value.m_real, value.m_imaginary), out result); } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] static bool INumberBase.TryConvertToTruncating(Complex value, [MaybeNullWhen(false)] out TOther result) { - // Complex numbers with an imaginary part can't be represented as a "real number" - // so we'll only consider the real part for the purposes of truncation. - - if (typeof(TOther) == typeof(byte)) - { - byte actualResult = (value.m_real >= byte.MaxValue) ? byte.MaxValue : - (value.m_real <= byte.MinValue) ? byte.MinValue : (byte)value.m_real; - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(char)) - { - char actualResult = (value.m_real >= char.MaxValue) ? char.MaxValue : - (value.m_real <= char.MinValue) ? char.MinValue : (char)value.m_real; - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(decimal)) - { - decimal actualResult = (value.m_real >= (double)decimal.MaxValue) ? decimal.MaxValue : - (value.m_real <= (double)decimal.MinValue) ? decimal.MinValue : (decimal)value.m_real; - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(double)) + if (typeof(TOther) == typeof(Complex)) { - double actualResult = value.m_real; - result = (TOther)(object)actualResult; + result = (TOther)(object)new Complex(value.m_real, value.m_imaginary); return true; } - else if (typeof(TOther) == typeof(Half)) - { - Half actualResult = (Half)value.m_real; - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(BFloat16)) - { - BFloat16 actualResult = (BFloat16)value.m_real; - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(short)) - { - short actualResult = (value.m_real >= short.MaxValue) ? short.MaxValue : - (value.m_real <= short.MinValue) ? short.MinValue : (short)value.m_real; - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(int)) - { - int actualResult = (value.m_real >= int.MaxValue) ? int.MaxValue : - (value.m_real <= int.MinValue) ? int.MinValue : (int)value.m_real; - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(long)) - { - long actualResult = (value.m_real >= long.MaxValue) ? long.MaxValue : - (value.m_real <= long.MinValue) ? long.MinValue : (long)value.m_real; - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(Int128)) - { - Int128 actualResult = (value.m_real >= +170141183460469231731687303715884105727.0) ? Int128.MaxValue : - (value.m_real <= -170141183460469231731687303715884105728.0) ? Int128.MinValue : (Int128)value.m_real; - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(nint)) - { - nint actualResult = (value.m_real >= nint.MaxValue) ? nint.MaxValue : - (value.m_real <= nint.MinValue) ? nint.MinValue : (nint)value.m_real; - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(BigInteger)) - { - BigInteger actualResult = (BigInteger)value.m_real; - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(sbyte)) - { - sbyte actualResult = (value.m_real >= sbyte.MaxValue) ? sbyte.MaxValue : - (value.m_real <= sbyte.MinValue) ? sbyte.MinValue : (sbyte)value.m_real; - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(float)) - { - float actualResult = (float)value.m_real; - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(ushort)) - { - ushort actualResult = (value.m_real >= ushort.MaxValue) ? ushort.MaxValue : - (value.m_real <= ushort.MinValue) ? ushort.MinValue : (ushort)value.m_real; - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(uint)) - { - uint actualResult = (value.m_real >= uint.MaxValue) ? uint.MaxValue : - (value.m_real <= uint.MinValue) ? uint.MinValue : (uint)value.m_real; - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(ulong)) - { - ulong actualResult = (value.m_real >= ulong.MaxValue) ? ulong.MaxValue : - (value.m_real <= ulong.MinValue) ? ulong.MinValue : (ulong)value.m_real; - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(UInt128)) - { - UInt128 actualResult = (value.m_real >= 340282366920938463463374607431768211455.0) ? UInt128.MaxValue : - (value.m_real <= 0.0) ? UInt128.MinValue : (UInt128)value.m_real; - result = (TOther)(object)actualResult; - return true; - } - else if (typeof(TOther) == typeof(nuint)) - { - nuint actualResult = (value.m_real >= nuint.MaxValue) ? nuint.MaxValue : - (value.m_real <= nuint.MinValue) ? nuint.MinValue : (nuint)value.m_real; - result = (TOther)(object)actualResult; - return true; - } - else - { - result = default; - return false; - } + + return Complex.TryConvertToTruncatingCore(new Complex(value.m_real, value.m_imaginary), out result); } /// diff --git a/src/libraries/System.Runtime.Numerics/tests/ComplexTests.cs b/src/libraries/System.Runtime.Numerics/tests/ComplexTests.cs index 5e1cf0fa21b0bf..1b333d261ee852 100644 --- a/src/libraries/System.Runtime.Numerics/tests/ComplexTests.cs +++ b/src/libraries/System.Runtime.Numerics/tests/ComplexTests.cs @@ -2255,4 +2255,338 @@ private static bool AreSameInfinity(double d1, double d2) double.IsPositiveInfinity(d1) == double.IsPositiveInfinity(d2); } } + + public class ComplexTests_Generic + { + [Theory] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 2.0)] + [InlineData(-1.5, 3.5)] + [InlineData(double.NaN, double.NaN)] + [InlineData(double.PositiveInfinity, double.NegativeInfinity)] + public static void Constructor_Double(double real, double imaginary) + { + var c = new Complex(real, imaginary); + Assert.Equal(real, c.Real); + Assert.Equal(imaginary, c.Imaginary); + } + + [Theory] + [InlineData(0.0f, 0.0f)] + [InlineData(1.0f, 2.0f)] + [InlineData(-1.5f, 3.5f)] + public static void Constructor_Float(float real, float imaginary) + { + var c = new Complex(real, imaginary); + Assert.Equal(real, c.Real); + Assert.Equal(imaginary, c.Imaginary); + } + + [Fact] + public static void StaticFields_Double() + { + Assert.Equal(0.0, Complex.Zero.Real); + Assert.Equal(0.0, Complex.Zero.Imaginary); + + Assert.Equal(1.0, Complex.One.Real); + Assert.Equal(0.0, Complex.One.Imaginary); + + Assert.Equal(0.0, Complex.ImaginaryOne.Real); + Assert.Equal(1.0, Complex.ImaginaryOne.Imaginary); + + Assert.True(double.IsNaN(Complex.NaN.Real)); + Assert.True(double.IsNaN(Complex.NaN.Imaginary)); + + Assert.True(double.IsInfinity(Complex.Infinity.Real)); + Assert.True(double.IsInfinity(Complex.Infinity.Imaginary)); + } + + [Theory] + [InlineData(2.0, 1.0, 3.0, 2.0, 5.0, 3.0)] + [InlineData(1.0, -1.0, -1.0, 1.0, 0.0, 0.0)] + public static void Add_Double(double r1, double i1, double r2, double i2, double expectedReal, double expectedImaginary) + { + var c1 = new Complex(r1, i1); + var c2 = new Complex(r2, i2); + var result = c1 + c2; + Assert.Equal(expectedReal, result.Real); + Assert.Equal(expectedImaginary, result.Imaginary); + } + + [Theory] + [InlineData(5.0, 3.0, 2.0, 1.0, 3.0, 2.0)] + [InlineData(1.0, 1.0, 1.0, 1.0, 0.0, 0.0)] + public static void Subtract_Double(double r1, double i1, double r2, double i2, double expectedReal, double expectedImaginary) + { + var c1 = new Complex(r1, i1); + var c2 = new Complex(r2, i2); + var result = c1 - c2; + Assert.Equal(expectedReal, result.Real); + Assert.Equal(expectedImaginary, result.Imaginary); + } + + [Theory] + [InlineData(1.0, 2.0, 3.0, 4.0, -5.0, 10.0)] // (1+2i)(3+4i) = 3+4i+6i+8i^2 = (3-8)+(4+6)i = -5+10i + [InlineData(1.0, 0.0, 2.0, 0.0, 2.0, 0.0)] + public static void Multiply_Double(double r1, double i1, double r2, double i2, double expectedReal, double expectedImaginary) + { + var c1 = new Complex(r1, i1); + var c2 = new Complex(r2, i2); + var result = c1 * c2; + Assert.Equal(expectedReal, result.Real, 10); + Assert.Equal(expectedImaginary, result.Imaginary, 10); + } + + [Theory] + [InlineData(1.0, 2.0)] + [InlineData(-3.0, 4.0)] + [InlineData(0.0, -5.0)] + public static void Conjugate_Double(double real, double imaginary) + { + var c = new Complex(real, imaginary); + var conj = Complex.Conjugate(c); + Assert.Equal(real, conj.Real); + Assert.Equal(-imaginary, conj.Imaginary); + } + + [Fact] + public static void Abs_Double() + { + Assert.Equal(5.0, Complex.Abs(new Complex(3.0, 4.0)), 10); + Assert.Equal(0.0, Complex.Abs(Complex.Zero)); + Assert.Equal(1.0, Complex.Abs(Complex.One), 10); + } + + [Fact] + public static void Abs_Float() + { + Assert.Equal(5.0f, Complex.Abs(new Complex(3.0f, 4.0f)), 5); + Assert.Equal(0.0f, Complex.Abs(Complex.Zero)); + } + + [Fact] + public static void FromPolarCoordinates_Double() + { + var c = Complex.FromPolarCoordinates(1.0, 0.0); + Assert.Equal(1.0, c.Real, 10); + Assert.Equal(0.0, c.Imaginary, 10); + + var c2 = Complex.FromPolarCoordinates(2.0, Math.PI / 2); + Assert.Equal(0.0, c2.Real, 10); + Assert.Equal(2.0, c2.Imaginary, 10); + } + + [Fact] + public static void GetMagnitude_GetPhase_Double() + { + var c = new Complex(3.0, 4.0); + Assert.Equal(5.0, c.GetMagnitude(), 10); + Assert.Equal(Math.Atan2(4.0, 3.0), c.GetPhase(), 10); + } + + [Fact] + public static void Log_Exp_Double() + { + // exp(log(z)) == z for non-zero z + var z = new Complex(3.0, 4.0); + var logZ = Complex.Log(z); + var expLogZ = Complex.Exp(logZ); + Assert.Equal(z.Real, expLogZ.Real, 10); + Assert.Equal(z.Imaginary, expLogZ.Imaginary, 10); + } + + [Fact] + public static void Sqrt_Double() + { + // sqrt(z)^2 == z for non-negative real z + var z = new Complex(4.0, 0.0); + var sqrtZ = Complex.Sqrt(z); + Assert.Equal(2.0, sqrtZ.Real, 10); + Assert.Equal(0.0, sqrtZ.Imaginary, 10); + + // sqrt(-1) == i + var minusOne = new Complex(-1.0, 0.0); + var sqrtMinusOne = Complex.Sqrt(minusOne); + Assert.Equal(0.0, sqrtMinusOne.Real, 10); + Assert.Equal(1.0, sqrtMinusOne.Imaginary, 10); + } + + [Fact] + public static void IsPredicates_Double() + { + Assert.True(Complex.IsFinite(new Complex(1.0, 2.0))); + Assert.False(Complex.IsFinite(new Complex(double.PositiveInfinity, 0.0))); + Assert.True(Complex.IsInfinity(new Complex(double.PositiveInfinity, 0.0))); + Assert.False(Complex.IsInfinity(new Complex(1.0, 2.0))); + Assert.True(Complex.IsNaN(new Complex(double.NaN, 0.0))); + Assert.False(Complex.IsNaN(new Complex(1.0, 2.0))); + Assert.True(Complex.IsRealNumber(new Complex(1.0, 0.0))); + Assert.False(Complex.IsRealNumber(new Complex(1.0, 2.0))); + Assert.True(Complex.IsComplexNumber(new Complex(1.0, 2.0))); + Assert.False(Complex.IsComplexNumber(new Complex(1.0, 0.0))); + Assert.True(Complex.IsImaginaryNumber(new Complex(0.0, 1.0))); + Assert.False(Complex.IsImaginaryNumber(new Complex(1.0, 1.0))); + } + + [Fact] + public static void Equality_Double() + { + var c1 = new Complex(1.0, 2.0); + var c2 = new Complex(1.0, 2.0); + var c3 = new Complex(1.0, 3.0); + + Assert.True(c1 == c2); + Assert.False(c1 == c3); + Assert.False(c1 != c2); + Assert.True(c1 != c3); + Assert.True(c1.Equals(c2)); + Assert.False(c1.Equals(c3)); + Assert.Equal(c1.GetHashCode(), c2.GetHashCode()); + } + + [Fact] + public static void ToString_And_Parse_Double() + { + var c = new Complex(1.5, -2.5); + string s = c.ToString(); + Assert.True(Complex.TryParse(s, null, out Complex parsed)); + Assert.Equal(c.Real, parsed.Real); + Assert.Equal(c.Imaginary, parsed.Imaginary); + } + + [Fact] + public static void ImplicitConversion_Double() + { + Complex c = 3.14; + Assert.Equal(3.14, c.Real); + Assert.Equal(0.0, c.Imaginary); + } + + [Fact] + public static void CreateChecked_Saturating_Truncating_Double() + { + // CreateChecked from int -> Complex + var c = Complex.CreateChecked(42); + Assert.Equal(42.0, c.Real); + Assert.Equal(0.0, c.Imaginary); + + // CreateSaturating: a large value that overflows Half should saturate to infinity + var cHalf = Complex.CreateSaturating(double.MaxValue); + Assert.True(Half.IsInfinity(cHalf.Real)); + Assert.Equal(Half.Zero, cHalf.Imaginary); + + // CreateTruncating: long.MaxValue -> float is finite (9.223372e+18f) + var cTrunc = Complex.CreateTruncating(long.MaxValue); + Assert.True(float.IsFinite(cTrunc.Real)); + Assert.Equal(0.0f, cTrunc.Imaginary); + } + + [Fact] + public static void Negate_Double() + { + var c = new Complex(3.0, -4.0); + var neg = -c; + Assert.Equal(-3.0, neg.Real); + Assert.Equal(4.0, neg.Imaginary); + } + + [Fact] + public static void Increment_Decrement_Double() + { + var c = new Complex(1.0, 2.0); + c++; + Assert.Equal(2.0, c.Real); + Assert.Equal(2.0, c.Imaginary); + c--; + Assert.Equal(1.0, c.Real); + Assert.Equal(2.0, c.Imaginary); + } + + [Fact] + public static void MaxMagnitude_MinMagnitude_Double() + { + var big = new Complex(3.0, 4.0); // |big| = 5 + var small = new Complex(1.0, 0.0); // |small| = 1 + + Assert.Equal(big, Complex.MaxMagnitude(big, small)); + Assert.Equal(small, Complex.MinMagnitude(big, small)); + } + + [Fact] + public static void Trig_Double() + { + // sin(0 + 0i) = 0 + var zero = new Complex(0.0, 0.0); + var sinZero = Complex.Sin(zero); + Assert.Equal(0.0, sinZero.Real, 10); + Assert.Equal(0.0, sinZero.Imaginary, 10); + + // cos(0 + 0i) = 1 + var cosZero = Complex.Cos(zero); + Assert.Equal(1.0, cosZero.Real, 10); + Assert.Equal(0.0, cosZero.Imaginary, 10); + } + + [Fact] + public static void Reciprocal_Double() + { + // 1 / (1 + 0i) = 1 + 0i + var one = Complex.One; + var recip = Complex.Reciprocal(one); + Assert.Equal(1.0, recip.Real, 10); + Assert.Equal(0.0, recip.Imaginary, 10); + + // 1 / (2 + 0i) = 0.5 + 0i + var two = new Complex(2.0, 0.0); + var recipTwo = Complex.Reciprocal(two); + Assert.Equal(0.5, recipTwo.Real, 10); + Assert.Equal(0.0, recipTwo.Imaginary, 10); + } + + [Fact] + public static void Pow_Double() + { + // (1 + 0i)^2 = 1 + 0i + var one = Complex.One; + var powered = Complex.Pow(one, 2.0); + Assert.Equal(1.0, powered.Real, 10); + Assert.Equal(0.0, powered.Imaginary, 10); + + // (2 + 0i)^3 = 8 + 0i + var two = new Complex(2.0, 0.0); + var cubed = Complex.Pow(two, 3.0); + Assert.Equal(8.0, cubed.Real, 10); + Assert.Equal(0.0, cubed.Imaginary, 10); + } + + [Fact] + public static void Log10_Double() + { + // log10(10 + 0i) ≈ 1 + var ten = new Complex(10.0, 0.0); + var log10 = Complex.Log10(ten); + Assert.Equal(1.0, log10.Real, 10); + Assert.Equal(0.0, log10.Imaginary, 10); + } + + [Fact] + public static void FloatType_BasicArithmetic() + { + var a = new Complex(1.0f, 2.0f); + var b = new Complex(3.0f, 4.0f); + + var sum = a + b; + Assert.Equal(4.0f, sum.Real); + Assert.Equal(6.0f, sum.Imaginary); + + var diff = b - a; + Assert.Equal(2.0f, diff.Real); + Assert.Equal(2.0f, diff.Imaginary); + + // (1+2i)(3+4i) = -5+10i + var product = a * b; + Assert.Equal(-5.0f, product.Real, 5); + Assert.Equal(10.0f, product.Imaginary, 5); + } + } }