Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

namespace System.Numerics
{
[Serializable]
public struct BigInteger : IFormattable, IComparable, IComparable<BigInteger>, IEquatable<BigInteger>
{
private const int knMaskHighBit = int.MinValue;
Expand Down
134 changes: 68 additions & 66 deletions src/System.Runtime.Numerics/src/System/Numerics/Complex.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ namespace System.Numerics
/// A complex number z is a number of the form z = x + yi, where x and y
/// are real numbers, and i is the imaginary unit, with the property i2= -1.
/// </summary>
[Serializable]
public struct Complex : IEquatable<Complex>, IFormattable
{
public static readonly Complex Zero = new Complex(0.0, 0.0);
Expand All @@ -29,20 +30,21 @@ public struct Complex : IEquatable<Complex>, IFormattable
// This value is used inside Asin and Acos.
private static readonly double s_log2 = Math.Log(2.0);

private double _real;
private double _imaginary;
// Do not rename, these fields are needed for binary serialization
private double m_real;
private double m_imaginary;

public Complex(double real, double imaginary)
{
_real = real;
_imaginary = imaginary;
m_real = real;
m_imaginary = imaginary;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we going to commentthat field names must not change, ornot

Copy link

@sharwell sharwell May 24, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💭 Assuming the upcoming test covers it, I wouldn't think the comment would be necessary. Seems like adding the comments is prone to a situation where one or more comments is omitted, and a reader incorrectly infers that the lack of a comment means the name can change.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer we add comments. I understand @sharwell's point, but at the same time, seeing a comment and avoiding a change entirely is a lot quicker than having to figure out why a test is failing, can be spotted in code reviews, etc.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe something inline like

            m_real = real; // Do not rename (binary serialization)
            m_imaginary = imaginary; // Do not rename (binary serialization)

We already do stuff like this in types that are hard bound to the runtime eg on string and array, although I guess there will be many more like this.
Your call @morganbr

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll go ahead with those comments in this change, but leave the overall process to @ViktorHofer and @krwq when they handle all of the other types.

}

public double Real { get { return _real; } }
public double Imaginary { get { return _imaginary; } }
public double Real { get { return m_real; } }
public double Imaginary { get { return m_imaginary; } }

public double Magnitude { get { return Abs(this); } }
public double Phase { get { return Math.Atan2(_imaginary, _real); } }
public double Phase { get { return Math.Atan2(m_imaginary, m_real); } }

public static Complex FromPolarCoordinates(double magnitude, double phase)
{
Expand Down Expand Up @@ -76,34 +78,34 @@ public static Complex Divide(Complex dividend, Complex divisor)

public static Complex operator -(Complex value) /* Unary negation of a complex number */
{
return new Complex(-value._real, -value._imaginary);
return new Complex(-value.m_real, -value.m_imaginary);
}

public static Complex operator +(Complex left, Complex right)
{
return new Complex(left._real + right._real, left._imaginary + right._imaginary);
return new Complex(left.m_real + right.m_real, left.m_imaginary + right.m_imaginary);
}

public static Complex operator -(Complex left, Complex right)
{
return new Complex(left._real - right._real, left._imaginary - right._imaginary);
return new Complex(left.m_real - right.m_real, left.m_imaginary - right.m_imaginary);
}

public static Complex operator *(Complex left, Complex right)
{
// Multiplication: (a + bi)(c + di) = (ac -bd) + (bc + ad)i
double result_Realpart = (left._real * right._real) - (left._imaginary * right._imaginary);
double result_Imaginarypart = (left._imaginary * right._real) + (left._real * right._imaginary);
return new Complex(result_Realpart, result_Imaginarypart);
double result_realpart = (left.m_real * right.m_real) - (left.m_imaginary * right.m_imaginary);
double 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, Complex right)
{
// Division : Smith's formula.
double a = left._real;
double b = left._imaginary;
double c = right._real;
double d = right._imaginary;
double a = left.m_real;
double b = left.m_imaginary;
double c = right.m_real;
double d = right.m_imaginary;

if (Math.Abs(d) < Math.Abs(c))
{
Expand All @@ -119,7 +121,7 @@ public static Complex Divide(Complex dividend, Complex divisor)

public static double Abs(Complex value)
{
return Hypot(value._real, value._imaginary);
return Hypot(value.m_real, value.m_imaginary);
}

private static double Hypot(double a, double b)
Expand Down Expand Up @@ -191,13 +193,13 @@ private static double Log1P(double x)
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._real, -value._imaginary);
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._real == 0 && value._imaginary == 0)
if (value.m_real == 0 && value.m_imaginary == 0)
{
return Zero;
}
Expand All @@ -206,12 +208,12 @@ public static Complex Reciprocal(Complex value)

public static bool operator ==(Complex left, Complex right)
{
return left._real == right._real && left._imaginary == right._imaginary;
return left.m_real == right.m_real && left.m_imaginary == right.m_imaginary;
}

public static bool operator !=(Complex left, Complex right)
{
return left._real != right._real || left._imaginary != right._imaginary;
return left.m_real != right.m_real || left.m_imaginary != right.m_imaginary;
}

public override bool Equals(object obj)
Expand All @@ -222,47 +224,47 @@ public override bool Equals(object obj)

public bool Equals(Complex value)
{
return _real.Equals(value._real) && _imaginary.Equals(value._imaginary);
return m_real.Equals(value.m_real) && m_imaginary.Equals(value.m_imaginary);
}

public override int GetHashCode()
{
int n1 = 99999997;
int realHash = _real.GetHashCode() % n1;
int imaginaryHash = _imaginary.GetHashCode();
int realHash = m_real.GetHashCode() % n1;
int imaginaryHash = m_imaginary.GetHashCode();
int finalHash = realHash ^ imaginaryHash;
return finalHash;
}

public override string ToString()
{
return string.Format(CultureInfo.CurrentCulture, "({0}, {1})", _real, _imaginary);
return string.Format(CultureInfo.CurrentCulture, "({0}, {1})", m_real, m_imaginary);
}

public string ToString(string format)
{
return string.Format(CultureInfo.CurrentCulture, "({0}, {1})", _real.ToString(format, CultureInfo.CurrentCulture), _imaginary.ToString(format, CultureInfo.CurrentCulture));
return string.Format(CultureInfo.CurrentCulture, "({0}, {1})", m_real.ToString(format, CultureInfo.CurrentCulture), m_imaginary.ToString(format, CultureInfo.CurrentCulture));
}

public string ToString(IFormatProvider provider)
{
return string.Format(provider, "({0}, {1})", _real, _imaginary);
return string.Format(provider, "({0}, {1})", m_real, m_imaginary);
}

public string ToString(string format, IFormatProvider provider)
{
return string.Format(provider, "({0}, {1})", _real.ToString(format, provider), _imaginary.ToString(format, provider));
return string.Format(provider, "({0}, {1})", m_real.ToString(format, provider), m_imaginary.ToString(format, provider));
}

public static Complex Sin(Complex value)
{
// We need both sinh and cosh of imaginary part. To avoid multiple calls to Math.Exp with the same value,
// we compute them both here from a single call to Math.Exp.
double p = Math.Exp(value._imaginary);
double p = Math.Exp(value.m_imaginary);
double q = 1.0 / p;
double sinh = (p - q) * 0.5;
double cosh = (p + q) * 0.5;
return new Complex(Math.Sin(value._real) * cosh, Math.Cos(value._real) * sinh);
return new Complex(Math.Sin(value.m_real) * cosh, Math.Cos(value.m_real) * sinh);
// There is a known limitation with this algorithm: inputs that cause sinh and cosh to overflow, but for
// which sin or cos are small enough that sin * cosh or cos * sinh are still representable, nonetheless
// produce overflow. For example, Sin((0.01, 711.0)) should produce (~3.0E306, PositiveInfinity), but
Expand All @@ -273,8 +275,8 @@ public static Complex Sin(Complex value)
public static Complex Sinh(Complex value)
{
// Use sinh(z) = -i sin(iz) to compute via sin(z).
Complex sin = Sin(new Complex(-value._imaginary, value._real));
return new Complex(sin._imaginary, -sin._real);
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)
Expand All @@ -299,18 +301,18 @@ public static Complex Asin(Complex value)
}

public static Complex Cos(Complex value) {
double p = Math.Exp(value._imaginary);
double p = Math.Exp(value.m_imaginary);
double q = 1.0 / p;
double sinh = (p - q) * 0.5;
double cosh = (p + q) * 0.5;
return new Complex(Math.Cos(value._real) * cosh, -Math.Sin(value._real) * sinh);
return new Complex(Math.Cos(value.m_real) * cosh, -Math.Sin(value.m_real) * sinh);
}

[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Cosh", Justification = "Cosh is the name of a mathematical function.")]
public static Complex Cosh(Complex value)
{
// Use cosh(z) = cos(iz) to compute via cos(z).
return Cos(new Complex(-value._imaginary, value._real));
return Cos(new Complex(-value.m_imaginary, value.m_real));
}

public static Complex Acos(Complex value)
Expand Down Expand Up @@ -345,12 +347,12 @@ public static Complex Tan(Complex value)
// 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._real;
double y2 = 2.0 * value._imaginary;
double x2 = 2.0 * value.m_real;
double y2 = 2.0 * value.m_imaginary;
double p = Math.Exp(y2);
double q = 1.0 / p;
double cosh = (p + q) * 0.5;
if (Math.Abs(value._imaginary) <= 4.0)
if (Math.Abs(value.m_imaginary) <= 4.0)
{
double sinh = (p - q) * 0.5;
double D = Math.Cos(x2) + cosh;
Expand All @@ -367,8 +369,8 @@ public static Complex Tan(Complex value)
public static Complex Tanh(Complex value)
{
// Use tanh(z) = -i tan(iz) to compute via tan(z).
Complex tan = Tan(new Complex(-value._imaginary, value._real));
return new Complex(tan._imaginary, -tan._real);
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)
Expand Down Expand Up @@ -497,7 +499,7 @@ private static void Asin_Internal (double x, double y, out double b, out double

public static Complex Log(Complex value)
{
return new Complex(Math.Log(Abs(value)), Math.Atan2(value._imaginary, value._real));
return new Complex(Math.Log(Abs(value)), Math.Atan2(value.m_imaginary, value.m_real));
}

public static Complex Log(Complex value, double baseValue)
Expand All @@ -513,26 +515,26 @@ public static Complex Log10(Complex value)

public static Complex Exp(Complex value)
{
double expReal = Math.Exp(value._real);
double cosImaginary = expReal * Math.Cos(value._imaginary);
double sinImaginary = expReal * Math.Sin(value._imaginary);
double expReal = Math.Exp(value.m_real);
double cosImaginary = expReal * Math.Cos(value.m_imaginary);
double sinImaginary = expReal * Math.Sin(value.m_imaginary);
return new Complex(cosImaginary, sinImaginary);
}

[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Sqrt", Justification = "Sqrt is the name of a mathematical function.")]
public static Complex Sqrt(Complex value)
{

if (value._imaginary == 0.0)
if (value.m_imaginary == 0.0)
{
// Handle the trivial case quickly.
if (value._real < 0.0)
if (value.m_real < 0.0)
{
return new Complex(0.0, Math.Sqrt(-value._real));
return new Complex(0.0, Math.Sqrt(-value.m_real));
}
else
{
return new Complex(Math.Sqrt(value._real), 0.0);
return new Complex(Math.Sqrt(value.m_real), 0.0);
}
}
else
Expand Down Expand Up @@ -565,35 +567,35 @@ public static Complex Sqrt(Complex value)
// 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;
if ((Math.Abs(value._real) >= s_sqrtRescaleThreshold) || (Math.Abs(value._imaginary) >= s_sqrtRescaleThreshold))
if ((Math.Abs(value.m_real) >= s_sqrtRescaleThreshold) || (Math.Abs(value.m_imaginary) >= s_sqrtRescaleThreshold))
{
if (double.IsInfinity(value._imaginary) && !double.IsNaN(value._real))
if (double.IsInfinity(value.m_imaginary) && !double.IsNaN(value.m_real))
{
// We need to handle infinite imaginary parts specially because otherwise
// our formulas below produce inf/inf = NaN. The NaN test is necessary
// so that we return NaN rather than (+inf,inf) for (NaN,inf).
return (new Complex(double.PositiveInfinity, value._imaginary));
return (new Complex(double.PositiveInfinity, value.m_imaginary));
}
else
{
value._real *= 0.25;
value._imaginary *= 0.25;
value.m_real *= 0.25;
value.m_imaginary *= 0.25;
rescale = true;
}
}

// This is the core of the algorithm. Everything else is special case handling.
double x, y;
if (value._real >= 0.0)
if (value.m_real >= 0.0)
{
x = Math.Sqrt((Hypot(value._real, value._imaginary) + value._real) * 0.5);
y = value._imaginary / (2.0 * x);
x = Math.Sqrt((Hypot(value.m_real, value.m_imaginary) + value.m_real) * 0.5);
y = value.m_imaginary / (2.0 * x);
}
else
{
y = Math.Sqrt((Hypot(value._real, value._imaginary) - value._real) * 0.5);
if (value._imaginary < 0.0) y = -y;
x = value._imaginary / (2.0 * y);
y = Math.Sqrt((Hypot(value.m_real, value.m_imaginary) - value.m_real) * 0.5);
if (value.m_imaginary < 0.0) y = -y;
x = value.m_imaginary / (2.0 * y);
}

if (rescale)
Expand All @@ -620,10 +622,10 @@ public static Complex Pow(Complex value, Complex power)
return Zero;
}

double valueReal = value._real;
double valueImaginary = value._imaginary;
double powerReal = power._real;
double powerImaginary = power._imaginary;
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);
Expand All @@ -641,8 +643,8 @@ public static Complex Pow(Complex value, double power)

private static Complex Scale(Complex value, double factor)
{
double realResult = factor * value._real;
double imaginaryResuilt = factor * value._imaginary;
double realResult = factor * value.m_real;
double imaginaryResuilt = factor * value.m_imaginary;
return new Complex(realResult, imaginaryResuilt);
}

Expand Down