This is how to properly implement IEquatable<>
and IFormattable
for structures:
The following is based on the following answers
Code
public struct Complex : IEquatable<Complex>, IFormattable
{
public Complex(float real, float imaginary)
{
Real=real;
Imaginary=imaginary;
}
public float Real { get; }
public float Imaginary { get; }
#region IEquatable Members
/// <summary>
/// Equality overrides from <see cref="System.Object"/>
/// </summary>
/// <param name="obj">The object to compare this with</param>
/// <returns>False if object is a different type, otherwise it calls <code>Equals(Complex)</code></returns>
public override bool Equals(object obj)
{
if (obj is Complex item)
{
return Equals(item);
}
return false;
}
/// <summary>
/// Checks for equality among <see cref="Complex"/> classes
/// </summary>
/// <returns>True if equal</returns>
public bool Equals(Complex other)
{
return Real.Equals(other.Real)
&& Imaginary.Equals(other.Imaginary);
}
/// <summary>
/// Calculates the hash code for the <see cref="Complex"/>
/// </summary>
/// <returns>The int hash value</returns>
public override int GetHashCode()
{
unchecked
{
int hc = -1817952719;
hc = (-1521134295)*hc + Real.GetHashCode();
hc = (-1521134295)*hc + Imaginary.GetHashCode();
return hc;
}
}
public static bool operator ==(Complex target, Complex other) { return target.Equals(other); }
public static bool operator !=(Complex target, Complex other) { return !target.Equals(other); }
#endregion
#region IFormattable Members
public override string ToString() => ToString("g");
public string ToString(string formatting) => ToString(formatting, CultureInfo.CurrentCulture.NumberFormat);
public string ToString(string formatting, IFormatProvider provider)
{
if (Imaginary!=0)
{
return Imaginary>0
? $"{Real.ToString(formatting, provider)}+{Imaginary.ToString(formatting, provider)}i"
: $"{Real.ToString(formatting, provider)}-{(-Imaginary).ToString(formatting, provider)}i";
}
else
{
return Real.ToString(formatting, provider);
}
}
#endregion
}
This ensures that ==
means exact equality. If you want to implement approximately equals, then add a method .ApproxEquals(Complex other, float delta)
or similar to implement that functionality.
The minimum delta is 2^(-22) = 0.00000023842
for a value of 1.0
Notice also that .GetHashCode()
isn't symmetric, in terms if you exchange the values if Real
and Imaginary
it will return a different result. This is a key feature that a lot of people mess up by just returning Real.GetHashCode() ^ Imaginary.GetHashCode()
which makes it Symmetric since the XOR
operator ^
is commutative.
Finally, the conversion to string is handled by .ToString()
with various overloads as needed. Best practice is to reference .NumberFormat
for the default format provider.