Decisions, Boolean logic, and safe branching

Computer Science I

condition?; true branch; false branch; continue.
Original learning diagram: condition? → true branch → false branch → continue.

A decision lets a program do different work for different inputs. To write one, state the condition in plain language first. Then translate it into comparisons, and check which branch will run at the boundaries.

We will classify one score, while keeping invalid scores separate from valid scores that need review.

Comparisons produce true or false #

A comparison such as <, <=, or == produces a Boolean result. Less-than excludes equality; less-than-or-equal includes it. = assigns a value, while == compares values. These symbols serve different purposes even when the surrounding names look similar.

To test an inclusive range, use low <= x && x <= high. The two comparisons ask separately whether x is at least the lower bound and at most the upper bound. The && requires both answers to be true.

Do not translate an algebraic chain directly into low <= x <= high. In C++, the first comparison produces a Boolean, and that result becomes part of the next comparison. It does not check both intended bounds on x.

The operator || requires at least one condition to be true. The operator ! reverses a Boolean result. These operations let us describe combinations of requirements and reasons for rejecting input.

Follow one branch through the example #

An if checks its condition. If the condition is true, its body runs. An else if is tried only when the earlier condition was false. An else handles the remaining case without another condition. Braces make each branch's body explicit.

#include <iostream>

int main() {
    const int score = 80;
    if (score < 0 || score > 100) {
        std::cout << "invalid\n";
    } else if (score >= 90) {
        std::cout << "excellent\n";
    } else if (score >= 70) {
        std::cout << "passing\n";
    } else {
        std::cout << "review needed\n";
    }
}

The score is initialized to 80. The first condition asks whether it is below zero or above one hundred. Both comparisons are false, so the invalid branch is skipped.

The second condition asks whether 80 is at least 90. It is false, so the excellent branch is skipped. The next condition asks whether 80 is at least 70. It is true, so that branch prints passing and a newline. The final else is skipped. Exactly one branch in this chain runs.

Compare this with several independent if statements. Independent conditions are all considered, so more than one body can run. A chain is useful here because the program needs one classification, not a collection of overlapping messages.

Check boundaries and branch order #

With score 90, the first check accepts the valid range and the excellent branch runs. With score 89, the excellent condition fails, but the passing condition succeeds. With score 70, passing still runs because the comparison includes equality. With score 69, the final else prints review needed. Scores -1 and 101 take the invalid branch first.

The thresholds must be ordered with their intended categories in mind. If the test for at least 70 came before the test for at least 90, a score of 95 would already qualify for the earlier branch. The later category would never be reached for that score.

Testing an ordinary value can miss this mistake. Values immediately below, at, and above a boundary show which comparisons include equality and which branch gets the first opportunity.

Short-circuit checks can protect an operation #

For &&, C++ evaluates the right operand only when the left operand is true.[1] For ||, it evaluates the right operand only when the left operand is false. This behavior is called short-circuit evaluation.

Consider count > 0 && total / count > limit. When count is zero, the first comparison is false. The second expression is not evaluated, so it does not divide by zero. When count is positive, the division is reached and its result is compared with limit. The division still follows its operand types: if total and count are integers, it uses integer division.

Putting the division first would lose this protection because C++ would need to evaluate it before checking count. A guard must precede the operation it makes safe. This same pattern appears in bounds guards: establish a valid position before reading an element.

Mathematical inputs need their own cases #

A formula can have preconditions, meaning requirements that must hold before using it. For a real square root, the argument must be nonnegative to produce a real number. For a quadratic equation, first check whether the leading coefficient is zero; that case does not permit the usual quadratic formula.

With a nonzero leading coefficient, the discriminant determines whether the usual real-root calculation applies. A negative discriminant has no real roots, zero gives a repeated root, and a positive discriminant gives two real roots. Even within the mathematical domain, floating-point rounding can make delicate cases require a more stable numerical method.

A triangle similarly requires more than positive sides. Each side must be shorter than the sum of the other two. Sides 1, 2, and 8 fail that requirement, so an area formula should not be applied just because all three inputs are positive.

A switch selects among discrete choices such as menu numbers. A case usually ends with break to avoid continuing into following cases unintentionally. A default handles unmatched choices.[2]

Practice with explained answers #

For an integer x, the expression !(x >= 0 && x <= 100) means that the inclusive range condition is false. This happens when x is below zero or above one hundred, giving the equivalent test x < 0 || x > 100. Values -1 and 101 are outside; 0 and 100 are inside.

These checks prepare not-found handling and valid indices in CS2 Lectures 3 through 5, menu choices in Lab 3, and the quadratic cases in Lab 4.

References

  1. ↑ C++ working draft: logical AND .
  2. ↑ C++ working draft: selection statements .