Skip to the content of the web site.

Lesson 1.10: Conditional statements

Previous lesson Next lesson


In many cases, we are posed with a problem, where based on some condition, we may have to take one action or another. One famous cartoon is

We start by answering a yes-no question, and based on that response, we then proceed to a second yes-no question. This second response then indicates the appropriate course of action to take.

For a flow diagram, we have two possible circumstances:

  1. If some condition is true, then we may have to perform some form of operation.
  2. If some condition is true, we may have to perform certain operations, otherwise, if the condition is false, we may have to perform other operations.

These are shown in the next two figures, respectively.


Figure 1. Performing operations if the condition is true.


Figure 2. Performing certain operations if the condition is true and other operations if the condition is false.

Suppose, for example, we want to define the absolute value function. This function takes a real value $x$ and returns $x$ if $x \ge 0$ and $-x$ if $x < 0$. A flow cart for the absolute value function would be as shown in Figure 3 while Figure 4 shows a plot of this function.


Figure 3. A flow chart for the absolute value function.


Figure 4. A plot of the absolute value function.

To date, the only Boolean-valued operators we have seen (operators that return true or false) are the comparison operators ==, !=, <=, <, > and >=. We would use the condition x >= 0. In order to program such a flow, we must however have a means of saying if something is true, do something, otherwise, do something else. For this, we have the conditional statement:

    if ( Boolean-valued condition ) {
        // The 'consequent block of statements' or 'consequent body'
        //  - statements to be executed if the condition is true
    } else {
        // The 'alternative block of statements' or 'alternative body'
        //  - statements to be executed if the condition is false
    }

This entire structure is referred to as a conditional statement even if the consequent and alternative bodies have one or more statements within them.

We can now use this and the comparison operator to implement the absolute value function. We will have to use an identifier that uses the alphabet, so we will use abs:

// Function declarations
double abs( double x );

// Function definitions
double abs( double x ) {
	if ( x >= 0 ) {
		return x;
	} else {
		return -x;
	}
}

Recall that each operator has a complementary operator, so if x >= 0 is true, then x < 0 must be false. If we use the complementary operator, we must swap the consequent and alternative bodies:

int abs( int x ) {
	if ( x < 0 ) {
		return -x;
	} else {
		return x;
	}
}

Next, let us implement the max function:

$\max(x, y) = \left\{ \begin{matrix} x & x\geq y\\ y & x<y \end{matrix} \right.$.

You will note that the two conditions are complementary, so this may be implemented as a single conditional statement:

double max( double x, double y ) {
	if ( x >= y ) {
		return x;
	} else {
		return y;
	}
}

In each of these cases, one or more arguments are passed to these functions, and based on the value of these arguments, specific code is executed.

Cascading conditional statements

Let us now look at another function: a clipped signal. Given a maximum absolute signal bound $b$, ${\rm clip}(x, b) = \left\{ \begin{matrix} -b & x < -b \\ x & -b \le x \le b \\ b & x > b \end{matrix} \right.$.

First, we must convert this into conditional statements:

  • If $x > b$, return $b$,
  • Otherwise,
    • if $x < -b$, return $-b$,
    • otherwise, return $x$.

We can draw this as a flow chart:

We can now implement this:

double clip( double x, double b ) {
	if ( x > b ) {
		return b;
	} else {
		if ( x < -b ) {
			return -b;
		} else {
			return y;
		}
	}
}

Notice that the alternative body is a single conditional statement. In this case, we can beautify the code by rewriting it as follows:

double clip( double x, double b ) {
	if ( x > b ) {
		return b;
	} else if ( x < -b ) {
		return -b;
	} else {
		return y;
	}
}

This can be drawn as an alternative flow chart as shown in Figure 5.


Figure 5. An if—else if—... sequence of statements.

If the first condition is true, the first consequent body is executed, otherwise if the second condition is true, the second consequent body is executed, otherwise the alternative body is executed.

This is also described as a cascading conditional statement, as it suggests a waterfall with multiple steps, as shown in Figure 6.


Figure 6. The cascading Song Khon Waterfall by Martin PĆ¼schel.

As another function that can be described by a cascading conditional statement, consider the tent function:

${\rm tent}(x) = \left\{ \begin{matrix} 0 & x \le -1 \\ x + 1 & -1 < x \le 0 \\ 1 - x & 0 < x \le 1 \\ 0 & x > 1 \end{matrix} \right.$.

While there are two conditions for the two intermediate cases, if we have already executed the first consequent body because $x \le -1$, then for the second condition, we do not have to test both $x > -1$ and $x \le 0$, but rather we only need to test if $x \le 0$, and similar for the third case. Thus, this function may be implemented as:

double tent( double x ) {
	if ( x <= -1.0 ) {
		return 0.0;
	} else if ( x <= 0.0 ) {
		return x + 1.0;
	} else if ( x <= 1.0 ) {
		return 1.0 - x;
	} else {
		return 0.0;
	}
}

The flow chart would have three conditions, as shown in Figure 7.


Figure 7. A cascading conditional statement with three conditions.

Note that we could have implemented the tent function in two different ways if we used the abs function:

double tent_3_cases( double x ) {
	if ( abs( x ) >= 1.0 ) {
		return 0.0;
	} else if ( x <= 0 ) {
		return x + 1;
	} else {
		return 1 - x;
	}
}

double tent_2_cases( double x ) {
	if ( abs( x ) >= 1.0 ) {
		return 0.0;
	} else {
		return 1 - abs( x );
	}
}

Note that we are not reducing the number of conditional statements that are being executed, because the abs function itself contains a conditional statement.

How not to use a cascading conditional

Novice programmers will often try to emphasize the condition of the alternative body, and so rather that writing the absolute value function as above, they will write it as:

// Function definitions
double abs( double x ) {
	if ( x >= 0 ) {
		return x;
	} else if ( x < 0 ) {
		return -x;
	}
}

The novice believes this is clearer, but it actually confuses experienced programmers. If you want to make sure that the condition is satisfied for the alternative body, you can use something called an assertion:

// Pre-processor include directives
#include <cassert>

// Function definitions
double abs( double x ) {
	if ( x >= 0 ) {
		return x;
	} else {
		assert( x < 0 );
		return -x;
	}
}

Now, when the alternative body is executed, it will first evaluate x < 0 and if this returns false, the program will terminate.

Common errors with cascading conditional statements

Two questions: What do you think the programmer meant by the following function, and what do you think actually happens when you call this function?

double square_wave( double x ) {
    if ( x <= -1.0 ) {
        return 0.0;
    } else if ( x < 0.0 ) {
        return -1.0;
    } else if ( x > 0.0 ) {
        return 1.0;
    } else if ( x >= 1.0 ) {
        return 0.0;
    } else {
        assert ( 0.0 == x );
        return 0.0;
    }
}

How would you correct this?

Conditional statements with no alternate body

The cardinal sine function or sinc function is defined as

${\rm sinc}(x) = \left\{ \begin{matrix} 1 & x = 0 \\ \frac{\sin(\pi x)}{\pi x} & x \ne 0 \end{matrix} \right.$.

In this function, we have a very special case when $x = 0$, and the general case is when $x \ne 0$. If we are dealing with special cases, it is often easier to put those conditions at the front.

// Pre-processor include directives
#include <cmath>

// Function declarations
double sinc( double x );

// Function definitions
double sinc( double x ) {
    // The special case when x = 0:
    if ( x == 0.0 ) {
        return 1.0;
    }

    // The general case
    return std::sin( 3.1415926535897932*x )/
                    (3.1415926535897932*x);
}

Using Boolean-valued functions

We are not restricted to using comparison operators in as conditions in conditional statements. Another choice is to use functions that return a Boolean value. Suppose, for example, we write the following function:

// Function declarations
bool is_divisible( int m, int n );

// Function definitions

// Return 'true' if m/n leaves no remainder, 'false' otherwise.
//  - that is, is 'm' an integer multiple of 'n'
bool is_divisible( int m, int n ) {
    return ((m % n) == 0);
}

We can then use this in another function where the conditional is a call to this function:

// Function declarations
int factor_out_bad_luck_once( int n );

// Function definitions
int factor_out_bad_luck_once( int n ) {
	if ( is_divisible( n, 13 ) ) {
		return n/13;
	} else {
		return n;
	}
}

While this is an obviously concocted example, it at least shows a point.

Recursive function calls

Recall our implementation of the fast sine function:

double fast_sin( double x ) {
    return ((
        -0.11073981636184074*x - 0.057385341027109429
    )*x + 1.0)*x;
}

This only returned a reasonable answer if $0 \le x \le \frac{\pi}{2}$. Suppose however, we have a value of $x$ that satisfied $-\frac{\pi}{2} \le x < 0$. If we tried to call this function with such a value of $x$, we would get the wrong answer. We could however, remember that $\sin(-x) = -sin(x)$ or equivalently $\sin(x) = -\sin(-x)$, so calculating $\sin(-0.3)$ can be done by calculating $-\sin(0.3)$.

Your first thought may be to do the following:

double fast_sin( double x ) {
    if ( x >= 0 ) {
        return ((
            -0.11073981636184074*x - 0.057385341027109429
        )*x + 1.0)*x;
    } else {
        assert( x < 0 );

        return -((
            -0.11073981636184074*(-x) - 0.057385341027109429
        )*(-x) + 1.0)*(-x);
    }
}

This is, however, a little unsatisfying. An alternate approach is to author the function as follows:

double fast_sin( double x ) {
    if ( x < 0 ) {
        return -fast_sin( -x );
    }

    assert( x >= 0.0 );

    return ((
        -0.11073981636184074*x - 0.057385341027109429
    )*x + 1.0)*x;
}

Let's see what happens if we call fast_sin( -0.3 ):

  • First, the argument is -0.3, so the parameter takes on the value x == -0.3.
  • The first condition is true, so the function calls itself but with the argument -x. -x evaluates to -(-0.3) == 0.3, so it is return -fast_sin( 0.3 ).
  • When fast_sin is called a second time, the parameter x == 3.0, so now the first condition is false, the assertion returns true, and the appropriate approximation 0.291845 (when displayed as six significant digits) is returned.
  • The returned value is negated, so -0.291845 is returned from the call fast_sin( -0.3 ).

We will have many opportunities to call recursive functions throughout this course.

Questions and practice:

1. Write a function median_of_three that takes three arguments and returns the median of those three.

double median_of_three( double a, double b, double c ) {
	// Enter your implementation here
}

2. Under the rules of the calendar in general use in Canada, a year is a leap year under the following conditions:

  • If the year is before 1 CE, it is never a leap year.
  • If the year is 1582 or before, a year is a leap year if it is a multiple of four.
  • If the year is after 1582, a year is a leap year if it is a multiple of four, unless it is a multiple of 100, in which case it is not, unless it is yet again a multiple of 400, in which case it is.

Write a function to take an argument and determine whether or not it is . Treat negative numbers as years BCE, and ignore the fact that technically 0 is not a year, just return false.

bool is_leap_year( int n );

Test your function by ensuring that 1200, 1500, 1600 and 2000 are leap years but 1700, 1800 and 1900 are not leap years. Also, test your code for a few other years to make sure you are return the correct value.

3. The normalized cardinal sine function, defined as $sinc(x) = \frac{\sin(\pi x)}{\pi x}$ for $x \ne 0$ and $1$ if $x = 0$ is a very common is an incredibly useful function for signal processing in all fields of engineering. The problem is, we have a singularity in the formula when $x = 0$, so we cannot implement the function as

double sinc( double x ) {
	return std::sin( 3.1415926535897932*x )/(3.1415926535897932*x);
}

In general, it's often a bad idea to evaluate formulas even close to a singularity, so modify the above function so that:

  • If $|x| < 10^{-4}$, return $1 - \frac{\pi^2}{6}x^2$,
  • otherwise, return \frac{\sin(\pi x)}{\pi x}$.

Test your function by calculating sinc(0.0001), which should execute the second case, and sinc(0.0000999999999999999), which should execute the first case. Are these values close?

Note: in your first-year calculus course, you will learn about Taylor series, which will explain where the above approximation comes from.

4. Write a function that implements the unit step function, defined as $u(x) = \left\{ \begin{matrix} 1 & x\geq 0\\ 0 & x<0 \end{matrix} \right.$.

5. Write a function that implements the maximum of three function. You will start with one condition that compares the first two parameters, but what goes into the consequent and alternative bodies?

// Function declarations
double max( double x, double y, double z );

// Function definitions
double max( double x, double y, double z ) {
    if ( x >= y ) {
        // Do something assuming x >= y
    } else {
        // Do something assuming y > x
    }
}

Rather than jumping to start programming, you should:

  1. state your solution in English
  2. draw a flow chart
  3. write approximately six tests
  4. fill in the function definition

6. Write a function that approximates an impulse function as follows:

  • The function takes two parameters: $x$ and $d$ (assume that $d$ is positive).
  • If $x = 0$, the function returns $\frac{1}{d}$.
  • If $x$ falls outside of $(-d, d)$, return $0$.
  • If $-d < x < 0$, the function returns a value that linearly increases from a value of $0$ to $\frac{1}{d}$ as $x$ goes from $-d$ to $0$.
  • Otherwise $0 < x < d$, and the function returns a value that linearly decreases from a value of $\frac{1}{d}$ down to $0$ as $x$ goes from $0$ to $d$.

The name of this function should be approx_impulse.

7. For Question 6, modify it as follows:

  • If $d = 0$, return $0$ if $x \ne 0$ and return $\frac{1.0}{0.0}$ if $x = 0$. This ratio evaluates to infinity.
  • If $d < 0$ then return the same function but called with $x$ and $-d$.
  • Otherwise, $d > 0$ so just do what was required in Question 6.

These two special cases should be in a conditional statement separate from the general case when $d > 0$.

8. The operational amplifier is very similar to the clipping function that we described earlier, but with slightly different properties. The operational amplifier is associated with an input signal $x$, a gain $g$ that is assumed to be greater than $0$, and two voltages $v_{\rm min}$ and $v_{\rm max}$.

  1. First, assert that $g > 0$ and that $v_{\rm min} < v_{\rm max}$.
  2. The output will be $gx$ unless either:
    • If $gx > v_{\rm max}$, $v_{\rm max}$ is returned.
    • If $gx < v_{\rm min}$, $v_{\rm min}$ is returned.

The function declaration should be:

// Function declarations
double op_amp( double x, double gain, double v_min, double v_max );

9. Modify the fast sine function by observing that if $\frac{\pi}{2} < x \le \pi$ then $\sin(x) = \sin(\pi - x)$ and where $0 \le \pi - x < \frac{\pi}{2}$ which falls in our acceptable range.

You may use the approximation of $\frac{\pi}{2}$ by 1.5707963267948966.

10. Modify the fast sine function in Question 9 by observing that if $\pi < x \le 2\pi$ then $\sin(x) = -\sin(2\pi - x)$ and where $0 \le 2\pi - x < \pi$ which falls in our condition for Question 9.

You may use the approximation of $\pi$ by 3.1415926535897932.

11. Modify the fast sine function in Question 10 by observing that if $x > 2\pi$ that $\sin(x) = \sin(x - 2\pi)$.

You may use the approximation of $2\pi$ by 6.2831853071795865.

At this point, you now should have a sine function that can return an approximation of $\sin(x)$ for any real value of $x$, even $x = -100$.


Previous lesson Next lesson