popolo - code simple..

popolo - code simple..

C#: complex numbers p2: defining equality

C#: complex numbers p2: defining equality

Dimitris Mageiras's photo
Dimitris Mageiras
·Apr 18, 2022·

5 min read

Table of contents

  • More advanced topics
  • References

This is the second part of the series "complex numbers in C#". In this part I will discuss for object equality and how this concept can be applied in our complex numbers class.

The mathematical definition of complex numbers equality is: "Two complex numbers are equal if and only if their real parts are equal and their imaginary parts are equal". The natural way of testing equality in c# is by applying the == operator. Will this work for our Z class? Let's try.

var z1 = new Z(1, 2);
var z2 = new Z(1, 2);
Console.WriteLine(z1 == z2);

The output:

False

The default behavior of == operator when applied to objects is to check for referential equality. To override this behavior, we will have to implement equality for our model (to match the mathematical definition of it) and to override the == operator to use our implementation. Referential equality will still exist if needed via ReferenceEquals method. If you want to know more on referential comparison skip to the end of the article.

Let's override == operator.

public static bool operator ==(Z z1, Z z2)
{
   return z1.Equals(z2);
}

When overriding equal operator (==), not-equal operator (!=) must also be overrided.

public static bool operator ==(Z z1, Z z2)
{
   return !z1.Equals(z2);
}

Now it's time to override Equals method which is used in both operator overrides above.

public override bool Equals(object? obj)
{
   if (obj == null) return false;
   if (!(obj is Z)) return false;  
   if (this.x != ((Z)obj).x) return false; 
   if (this.y != ((Z)obj).y) return false;
   return true;
}

Computers must take care more details than mathematics before deciding for equality: Null check, type check and x ~ y comparisons, before equals returns boolean true. And to make things more difficult, identical values represented by floating point numbers could be mistaken as not being equal and vice versa. See more on this, in advanced topics at the end of this chapter.

The last detail to take care, is the override of GetHashCode method. The implementation is based on the combination of x, y properties of Z.

public override int GetHashCode()
{
   return HashCode.Combine(this.x, this.y);
}

Now, we can test again if z1, z2 are equal:

var z1 = new Z(1, 2);
var z2 = new Z(1, 2);
Console.WriteLine(z1 == z2);

The output:

True

More advanced topics

Testing floating point equality

To be considered equal, two floating point values must represent identical values. However, because of differences in precision between values, or because of a loss of precision by one or both values, floating-point values that are expected to be identical often turn out to be unequal because of differences in their least significant digits. As a result, calls to the Equals method to determine whether two values are equal, or calls to the CompareTo method to determine the relationship between two Double values, often yield unexpected results. Calculated values that follow different code paths and that are manipulated in different ways often prove to be unequal. In cases where a loss of precision is likely to affect the result of a comparison, you can adopt any of the following alternatives to calling the Equals or CompareTo method:

  1. Call the Math.Round method to ensure that both values have the same precision. The following example modifies a previous example to use this approach so that two fractional values are equivalent. The problem of precision still applies to rounding of midpoint values. For more information, see the Math.Round(Double, Int32, MidpointRounding) method.
  2. Test for approximate equality rather than equality. This requires that you define either an absolute amount by which the two values can differ but still be equal, or that you define a relative amount by which the smaller value can diverge from the larger value. The following example uses the latter approach to define an IsApproximatelyEqual method that tests the relative difference between two values. It also contrasts the result of calls to the IsApproximatelyEqual method and the Equals(Double) method.

Five guarantees of equivalence

  1. The reflexive property: x.Equals(x) returns true.
  2. The symmetric property: x.Equals(y) returns the same value as y.Equals(x).
  3. The transitive property: if (x.Equals(y) && y.Equals(z)) returns true, then x.Equals(z) returns true.
  4. Successive invocations of x.Equals(y) return the same value as long as the objects referenced by x and y aren't modified.
  5. Any non-null value isn't equal to null. However, x.Equals(y) throws an exception when x is null. That breaks rules 1 or 2, depending on the argument to Equals.

Referential comparison

Reference equality means that two object references refer to the same underlying object. ReferenceEquals method is always there to determine whether two references refer to the same object. The concept of reference equality applies only to reference types. Value type objects cannot have reference equality because when an instance of a value type is assigned to a variable, a copy of the value is made. Therefore you can never have two unboxed structs that refer to the same location in memory. Furthermore, if you use ReferenceEquals to compare two value types, the result will always be false, even if the values that are contained in the objects are all identical. This is because each variable is boxed into a separate object instance.

Value equality

Value equality means that two objects contain the same value or values. For primitive value types such as int or bool, tests for value equality are straightforward, you can use the == operator. For most other types, testing for value equality is more complex because it requires that you understand how the type defines it. For classes and structs that have multiple fields or properties, value equality is often defined to mean that all fields or properties have the same value. For example, two Point objects might be defined to be equivalent if pointA.X is equal to pointB.X and pointA.Y is equal to pointB.Y. For records, value equality means that two variables of a record type are equal if the types match and all property and field values match. However, there is no requirement that equivalence be based on all the fields in a type. It can be based on a subset. When you compare types that you do not own, you should make sure to understand specifically how equivalence is defined for that type.

References

 
Share this