Unit 9: Logical Expressions
Learning Objectives
After this unit, students should:
- be able to read and write logical expressions in C using various logical operators, including
==
,<
,<=
,<
,>=
,!=
,&&
,||
and!
; - be aware of the
bool
type and its valuestrue
andfalse
, and the need to includestdbool.h
to use it in C; - be aware that in CS1010, we must never use
int
to indicate a true/false value; - be aware of short-circuiting in logical expression;
- be aware of the CS1010 conventing of naming a boolean variable with the prefix
is_
orhas_
; - be able to write a logical expression in appropriate order to exploit short-circuiting towards more efficient code
Representing a Boolean Value
You have seen some basic logical expressions in Unit 8. n == 0
, score >= 5
, y > x
, are all logical expressions. They evaluate to either true or false.
We call a type that can contain either true or false as a Boolean data type, named after George Boole, a mathematician.
The Boolean data type in C is named bool
. It can hold two values: true
or false
. All three of bool
, true
, and false
are keywords introduced in modern C. To use them, you need to include the file stdbool.h
at the top of your program.
Use bool
is considered a cleaner way of representing true and false in C. Classically, C defines the numeric value 0 to be false, and everything else to be true. So, you can write code like this:
1 2 3 4 5 |
|
The above is harder to understand and should be avoided. A cleaner way is to write:
1 2 3 4 |
|
Although not required by C, we will name a bool
variable with a prefix is_
or has_
as a convention.
The code above can also be written as:
1 2 3 4 |
|
The comparison with true
is redundant, however, and should be skipped.
Logical Operators
Just like we can perform arithmetic operations on integers and real numbers, we can perform logical operations on boolean values. These allow us to write complex logical expressions.
Consider the example problem: Write a function that, given the birth year of a person, determine if he or she belongs to Generation Z, defined as someone whose birth is between 1995 and 2005, inclusive.
We can write the function as follows using what we have known:
1 2 3 4 5 6 7 8 9 |
|
To be in Generation Z, both conditions birth_year >= 1995
and birth_year <= 2005
must be true. We can use the logical AND &&
operator to simplify the code above to:
1 2 3 4 5 6 7 |
|
or simply:
1 2 3 4 |
|
The AND operator, &&
, evaluates to true if and only if both operands are true.
Common Error
A common mistake by a new C programmer is to write 1995 <= birth_year <= 2005
as the logical expression. Unfortunately, in C, we cannot chain the comparison operators together.
What if we want to write a function to determine if someone is NOT part of Generation Z? This means that they are born either before 1995 or after 2005. To have an expression that evaluates to true if either one of two expressions is true, we can use the OR operator, ||
.
1 2 3 4 |
|
Generally, we prefer to write functions that check for the positives, as it is generally easier to think in terms of the positives. So the example is_not_gen_z
above is for illustration purposes only, we do not encourage you to write functions that check for the negatives. In any case, if we want to check if someone is not a Generation Z, we can use the !
NOT operator.
1 2 3 |
|
This operator can be used as part of the boolean expression:
1 2 3 4 |
|
Short-Circuiting
When evaluating the logical expressions that involve &&
and ||
, C uses "short-circuiting". This means that, if we already know, for sure, that a logical expression is true or is false, there is no need to continue the evaluation. The corresponding true
and false
value will be returned.
Consider the following:
1 2 3 4 |
|
If the argument birth_year
is 1970
, then, the expression (birth_year >= 1995)
already evaluates to false
, and the whole statement is false. We do not need to evaluate the second expression (birth_year <= 2005)
.
Similarly, for
1 2 3 4 |
|
When birth_year
is 1970
, the expression (birth_year < 1995)
is true
, so we know that the whole statement is true
. There is no need to check if (birth_year > 2005)
.
In both examples above, the savings due to short-circuiting is not much -- since we are comparing two numbers, and there is no side effects in comparing birth_year
to 2005
. But, let's suppose that we introduce two functions with side effects (of printing to the screen):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
When we call is_gen_z(1984)
, you might expect too old..not too young..
to be printed, but due to short-circuiting, the code only prints too old..
.
Another reason to keep short-circuiting in mind is that the order of the logical expressions matter: we would want to put the logical expression that involves more work in the second half of the expression. Take the following example:
1 2 3 |
|
Checking whether a number is below 100,000 is easier than checking if a number is prime. So, we can skip checking for primality if the number
is too big. Compare this to:
1 2 3 |
|
Suppose number
is a gigantic integer, then we would have spent lots of effort checking if number
is a prime, only to find out that it is too big anyway!
Problem Sets
Problem 9.1
Given two bool
variables, a
and b
, there are four possible combinations of true
false
values. What are the values of a && b
, a || b
, and !a
for each of these combinations? Fill in the table below.
a |
b |
a && b |
a || b |
!a |
---|---|---|---|---|
true |
true |
|||
true |
false |
|||
false |
true |
|||
false |
false |
Problem 9.2
Consider the function below, which aims to return the maximum value given three numbers.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
(a) What is wrong with the code above?
(b) Give a sample test value of a
, b
, and c
that would expose the bug.
(c) Fix the code above to remove the bug.
(d) Replace the three if
statements in the code above with if
-else
statements. Draw the corresponding flowchart.
Problem 9.3
Write a function that takes in a blood pressure measurement, and prints either low
, ideal
, pre-high
, and high
depending on the input values. The blood pressure is given as two long
values, the systolic and the diastolic. The text to be printed depends on the range, depicted in the figure below.
1 2 3 4 |
|
The figure does not say how to classify the data if the values fall exactly on the boundary of two regions. In this case, you can classify it into either region.