Concepts / Overloading Arithmetic Operators for Complex Numbers

Overloading Arithmetic Operators for Complex Numbers

Comparison operators such as == and != are only meaningful for a user-defined class once you define operator== and operator!= for it.

  • Programming

Predicting Equality for Two Complex Numbers

Suppose you have built a class for complex numbers, and you write a == b with two complex values. The language does not know on its own what it means for two complex numbers to be equal. A complex number has two parts, a real part and an imaginary part, and the answer depends on what you decided equality should mean for your type. This article follows a code-first approach: you will see the comparison run, predict what it returns, and then trace the definitions that produced the result.

What do you think happens?

Take a with real part 5 and imaginary part 2, and b with real part 5 and imaginary part -2. Does a == b evaluate to true or false?

  • true

  • false

Reveal answer
Answer:

false

The real parts match, but the imaginary parts (2 and -2) do not. The equality definition returns true only when every part matches, so the overall result is false. The trace in the next sections shows exactly where this comes from.

Why a Type Needs Its Own Comparison Definitions

Definition

Comparison operators such as == and != are only meaningful for a user-defined class once you define operator== and operator!= for it.

The two operators are written as ordinary functions named operator== and operator!=. The language looks for these names when it sees the == or != symbol between two values of your class. Each one takes two complex values as its parameters and returns a bool, which is either true or false. Without a definition like this, the comparison symbol has no meaning for your type, so the program needs you to supply one.

The same idea applies to arithmetic. The language does not automatically know what it means to add two of your values or subtract one from another. Operator overloading is the mechanism that lets you attach your own behavior to an operator symbol when its operands are your type. An overloaded arithmetic operator is a function whose name is the operator symbol, such as operator+, and whose body defines what the operator means for the user-defined type.

cpp
Notes on the code
  • operator== takes two complex values and returns bool
  • operator!= takes two complex values and returns bool
  • The bodies are the two definitions described in the source

Tracing How operator== Compares Each Part

The equality operator does not compare whole complex numbers in a single step. It asks two separate questions. First, it checks whether the real part of a equals the real part of b, using a.real() and b.real(). Second, it checks whether the imaginary part of a equals the imaginary part of b, using a.imag() and b.imag(). The two results are joined with the && operator, which means both checks must succeed for the overall result to be true.

Because the comparison uses ==, each part is compared exactly. Two complex numbers whose parts differ, even slightly, are not equal under this definition. The operator returns true only in the case where every part matches.

Worked example

Equality of two numbers sharing a real part

Let a = 5 + 2i and b = 5 - 2i. Both have real part 5, but their imaginary parts are opposite. Trace the result of a == b using the source definition.

  1. 1
    Check the real parts

    a.real() is 5 and b.real() is 5, so a.real() == b.real() evaluates to true.

  2. 2
    Check the imaginary parts

    a.imag() is 2 and b.imag() is -2, so a.imag() == b.imag() evaluates to false.

  3. 3
    Join the checks with &&

    true && false evaluates to false, because both checks must succeed.

  4. 4
    Return the result

    operator== returns false to the caller.

Result

a == b is false, even though the real parts match.

Reusing Equality to Define Inequality

The inequality operator does not repeat the component checks. Its definition is return !(a==b), which first evaluates a==b and then negates the result. The source describes this as implementing inequality in terms of equality, so operator!= depends on operator== and stays consistent with it.

a, b passed inevaluate a == boperator== resulttrue or falseapply !negate the resultreturn boolcall operator==returns to callernegate with !inequality answer
How does control flow move from operator!= into operator== and back, and how does negating the result produce the inequality answer?

Trace the flow for the pair from the previous section. operator== evaluates to false, so the expression !(false) becomes true, and a != b returns true. The two operators give opposite answers for every pair, because one is literally the negation of the other.

This design has a practical consequence. If the rule for what counts as equal changes, you only need to change operator==. The inequality operator follows automatically, because it is defined in terms of the equality result.

Mistakes in Comparison Operator Overloads

When a comparison returns a result you did not expect, the cause is usually in one of the two definitions. Most divergences come from a single check being wrong, such as using || instead of &&, or checking only one part.

  • Common mistake

    Using || instead of && in operator==

    What it looks like

    With a = 5 + 2i and b = 5 - 2i, the expression a.real() == b.real() || a.imag() == b.imag() evaluates to true || false, which is true.

    Why it fails

    || succeeds when either part matches, so a single matching part makes two different numbers compare as equal.

    Do this instead

    Use && so that both the real and imaginary checks must succeed.

  • Common mistake

    Checking only one part

    What it looks like

    If operator== compares only a.real() and b.real(), then 5 + 2i and 5 - 2i are reported as equal.

    Why it fails

    The definition ignores the imaginary part, so differences in that part are invisible to the comparison.

    Do this instead

    Compare both the real and imaginary parts and join the checks with &&.

  • Common mistake

    Writing operator!= with its own separate component checks

    Why it fails

    The two operators can drift apart. If the equality rule changes, the duplicated inequality checks must be changed separately.

    Do this instead

    Define operator!= as !(a == b) so it reuses the equality logic.

To locate a divergence, start from the operator that produced the unexpected value. If it is operator!=, look at operator== first, because the inequality result is only as correct as the equality logic it negates. Then check each part of the equality definition on its own, since a single wrong check is the most common cause.

Pass-by-Value and the Caller's Copy

Arguments passed by value are copied, so the function can modify its parameter freely without changing the caller's value. This is what makes the arithmetic definitions safe to write. Inside operator+, the parameter a is a copy of the caller's first value, so updating a does not touch the caller's variable.

x(2, 3)x(2, 3)y(1, 4)y(1, 4)a (copy)(2, 3)a (copy)(3, 7)z(3, 7)
How do the caller's original complex numbers compare before and after an operator+ call, and where does the copy live?

The diagram shows the key asymmetry. The copy named a changes, while the caller's x stays as it was. The copy is the thing being updated, and the caller's x keeps its original value of 2 + 3i. The returned value is what carries the result out of the function, which is why the definition ends with a return statement rather than relying on the parameter to communicate the answer.

Tracing operator+ Step by Step

Start with the addition definition. Its name is operator+, and it accepts two parameters, both of type complex. The body does not compute a fresh sum in a separate variable. Instead, it applies the compound assignment a += b to the first parameter and returns the result. The body relies on a += b being meaningful for the type, which is why the compound assignment matters here.

cpp
Notes on the code
  • Two complex parameters, both passed by value
  • The compound assignment updates the first parameter, which is a copy
  • The updated copy is returned to the caller
calleroperator+copy azpass x and y by valuea += b updates the copyupdated a is (3, 7)return az receives (3, 7)
In what order do the arguments, the function body, and the returned value flow, and what value does the caller receive?

What do you think happens?

Suppose x is 2 + 3i and y is 1 + 4i, and the caller writes z = x + y. What value does z receive, and what value does x hold once the expression has finished?

Reveal answer
Answer:

z receives 3 + 7i, and x still holds 2 + 3i.

Inside operator+, a is a copy of x (2 + 3i) and b is a copy of y (1 + 4i). a += b makes the copy a equal to 3 + 7i, and that copy is returned. The caller's x was never modified, because the parameter a is a separate value.

Binary and Unary Forms Side by Side

A binary operator takes two operands, so its definition has two parameters. The source gives the same pattern for subtraction, multiplication, and division. Each body performs the matching compound assignment on the first parameter and returns it. Subtraction uses a -= b, multiplication uses a *= b, and division uses a /= b. Only the operator symbol and the compound assignment change; the structure stays the same.

complexreturn typeoperator+operator symbol names thefunctioncomplex afirst operand, passed byvaluecomplex bsecond operand, passed byvalue
How many operands does a binary form take, and what does it return?

A unary operator takes exactly one operand. Unary minus has a different shape from the binary definitions. Its parameter list has a single complex value, and its body does not use a compound assignment. Instead, it builds and returns a new complex value whose real part is the negation of the original real part and whose imaginary part is the negation of the original imaginary part. If a has real part $a_r$ and imaginary part $a_i$, the result is $(-a_r, -a_i)$.

cpp
Notes on the code
  • One parameter, so the operator takes one operand
  • No compound assignment; a new value is constructed
  • The result is returned and the operand is not modified in place
FeatureBinary definition (operator+)Unary definition (operator-)
Number of parametersTwoOne
BodyCompound assignment on the first parameterBuilds a new value from negated parts
What is returnedThe updated copy of the first operandA new complex value
Operand modified in place?Only the copy, never the caller's valueNo; a fresh value is constructed

Counting parameters and describing what each form returns

The unary case shows the contrast clearly. The operand is not modified in place as a compound assignment would do. The function constructs a fresh value and hands it back, whereas in the binary definitions the copy of the first operand is the thing being updated.

Practice Locating a Bug

EASY

Suppose a teammate wrote operator== as a.real() == b.real() || a.imag() == b.imag() and reports that 3 + 4i and 3 + 9i are equal. Which definition contains the bug, and what single change corrects the result? Then predict the result of a != b for the same pair after your fix.

Hints
  • Ask which operator is responsible for the equality decision, since operator!= only negates it.
  • Think about what || returns when only one of its two checks succeeds.
  • Once operator== is fixed, operator!= updates with no change to its own body.

Key Takeaways

  • A user-defined type needs its own operator== and operator!= because the language cannot infer what equality means for a complex number.
  • operator== returns true only when the real parts and the imaginary parts are both equal, joined with &&, and operator!= is defined as !(a==b) so it stays consistent with equality.
  • Most comparison divergences come from one wrong check, such as using || instead of && or comparing only one part, so inspect the equality definition first.
  • Arithmetic operators take pass-by-value operands, so a binary definition like operator+ updates a copy of the first operand and returns it, leaving the caller's values unchanged.
  • A unary operator such as operator- takes one parameter and returns a new value, while a binary operator takes two parameters.

Related concepts

More concepts starting with O