COMP10110 Computer Programming I - Lecture 5: Conditionals and Boolean Expressions

COMP10110 Computer Programming I - Lecture 5: Conditionals and Boolean Expressions

This lecture covers the fundamental concepts of conditional statements in C programming, including if, if...else, and nested if...else constructs. It delves into boolean expressions, equality and relational operators, logical operators, the nuances of floating-point comparisons, and operator precedence. Special attention is given to C's unique handling of boolean values as integers and the introduction of the bool type in C11.

Conditionals: The if Statement

  • Concept of Decision Making

    • We make decisions in daily life based on conditions (e.g., "If I get hungry, I will eat my lunch").

    • These are called conditional sentences, comprising two parts:

      1. Condition Part: The if clause (e.g., "If I get hungry").

      2. Action Part: The consequence (e.g., "I will eat my lunch").

    • The action is executed only if the condition is met.

  • Testing Conditions

    • A condition can be rephrased as a question with a yes/no answer (e.g., "Am I hungry?").

    • A "yes" answer means the condition is true (or evaluates to true).

    • A "no" answer means the condition is false (or evaluates to false).

    • The action is performed exclusively when the condition is true.

  • Conditionals in C

    • The C programming language provides conditional statements to implement this concept.

    • The keyword if is used to define a conditional statement.

  • Structure of if Statement

    if (condition) {
        // Compound statement (action) to be executed if condition is true
    }
    
    • The condition is a boolean expression enclosed in round brackets (). A boolean expression is one that evaluates to either true or false.

    • The compound statement is a set of statements contained within a pair of braces { }. This block is executed only if the condition evaluates to true; it is skipped if the condition evaluates to false.

  • Example: if Statement

    /* Program to show the use of the if statement. */
    #include <stdio.h>
    #define PASS_MARK 40
    
    int main(void) {
        int mark;
        printf("Enter a mark (as an integer):\n");
        scanf("%d", &mark);
    if (mark &gt;= PASS_MARK) { // Condition: mark greater than or equal to 40
        printf("A pass mark.\n");
    }
    return 0;
    

    }

    • In this example, mark >= PASS_MARK is the boolean expression. If the entered mark is 40 or higher, the message "A pass mark." will be printed.

  • Common Error: Semicolon after if Condition

    • Placing a semicolon ; immediately after the if condition is a common mistake that changes the program's logic.

    • Incorrect Example:
      c if (mark >= PASS_MARK); /* error */ { printf("A pass mark.\n"); }

    • Effects of this error:

      1. The if statement's body is considered empty. The if statement does nothing, even if its condition is true.

      2. The statement printf("A pass mark.\n"); is treated as a separate, independent statement and will always be executed, regardless of whether mark >= PASS_MARK evaluates to true or false.

Conditionals: The if...else Statement

  • Purpose

    • The if...else statement allows specifying different actions for true and false conditions.

  • Structure

    if (condition) {
        // Compound statement 1 (executed if condition is true)
    } else {
        // Compound statement 2 (executed if condition is false)
    }
    
    • If condition evaluates to true, only Compound statement 1 (after if) is executed.

    • If condition evaluates to false, only Compound statement 2 (after else) is executed.

  • Example: if...else Statement

    /* Program to show the use of the if...else statement. */
    #include <stdio.h>
    #define PASS_MARK 40
    
    int main(void) {
        int mark;
        printf("Enter a mark (as an integer):\n");
        scanf("%d", &mark);
    if (mark &gt;= PASS_MARK) {
        printf("A pass mark.\n");
    } else {
        printf("Not a pass mark.\n");
    }
    return 0;
    

    }

Conditionals: Nested if...else Statements

  • Purpose

    • Used to evaluate multiple conditions by placing if...else statements inside other if...else statements.

  • Equivalency

    • C compilers treat different nesting arrangements as equivalent if their logic produces the same outcome.

    • The lecture provides two examples (Example 1 and Example 2) that are functionally equivalent.

  • Example 1: Traditional Nested if...else

    #include <stdio.h>
    #define PASS_MARK 40
    #define DISTINCTION_MARK 70
    
    int main(void) {
        int mark;
        printf("Enter a mark (as an integer):\n");
        scanf("%d", &mark);
    if (mark &gt;= DISTINCTION_MARK) {
        printf("Distinction\n");
    } else { // If not distinction, check for pass or fail
        if (mark &gt;= PASS_MARK) {
            printf("Pass\n");
        } else {
            printf("Fail\n");
        }
    }
    return 0;
    

    }

  • Example 2: else if Ladder (More Compact)

    #include <stdio.h>
    #define PASS_MARK 40
    #define DISTINCTION_MARK 70
    
    int main(void) {
        int mark;
        printf("Enter a mark (as an integer):\n");
        scanf("%d", &mark);
    if (mark &gt;= DISTINCTION_MARK) {
        printf("Distinction\n");
    } else if (mark &gt;= PASS_MARK) { // Only checked if first condition is false
        printf("Pass\n");
    } else { // Only reached if both previous conditions are false
        printf("Fail\n");
    }
    return 0;
    

    }

Equality and Relational Operators

  • Comparison of Numbers

    • Six primary ways to compare two numbers, x and y:

      • Equal: is x equal to y? (C operator: x == y)

      • Not Equal: is x not equal to y? (C operator: x != y)

      • Greater Than: is x greater than y? (C operator: x > y)

      • Less Than: is x less than y? (C operator: x < y)

      • Greater Than or Equal: is x greater than or equal to y? (C operator: x >= y)

      • Less Than or Equal: is x less than or equal to y? (C operator: x <= y)

  • Categories of Operators

    • Equality Operators: == and !=

    • Relational Operators: >, <, >=, <=

  • Evaluation Result

    • Each of these operators evaluates to either true or false.

  • Important Note: Assignment vs. Equality

    • Take extreme care not to confuse the equality operator (==) with the assignment operator (=).

      • x == y checks if x is equal to y.

      • x = y assigns the value of y to x.

Equality Operators – Floating-Point Numbers

  • The Issue with Floating-Points

    • Care is required when using equality operators (==, !=) with floating-point numbers (e.g., float, double).

    • Integers are stored exactly, so this issue doesn't arise with int types.

    • Floating-point numbers can have precision issues due to their internal binary representation, meaning that what appears mathematically equal might not be exactly equal in computer memory.

  • Example of Floating-Point Precision Error

    double x = 0.1;
    double y = 0.3 - 0.1 - 0.1; // Mathematically y should be 0.1
    double diff = x - y;
    printf("diff is %le\n", diff); /* Output: diff is 2.775558e-17 */
    
    • In this case, x == y would evaluate to false because y is not exactly 0.1 due to internal precision. The diff shows a very small, non-zero difference.

  • Solution: Comparing with a Tolerance Value (EPS)

    • To robustly compare two floating-point variables, x and y, for equality, check if the absolute difference between them is less than a small tolerance value (often called EPS or epsilon).

    • This approach accounts for minor floating-point inaccuracies.

    • Formula: fabs(x - y) < EPS

    • fabs() function (from <math.h> in C) calculates the absolute value for floating-point numbers.

    • EPS should be a very small positive number, e.g., 1e-10 or 1e-12.

  • Example using Tolerance

    #include <stdio.h>
    #include <math.h> // For fabs
    
    // Define a small tolerance value
    #define EPS 1e-10 
    
    int main(void) {
        double x = 0.1;
        double y = 0.3 - 0.1 - 0.1;
    if (fabs(x - y) &lt; EPS) {
        printf("equal\n");  /* Output: equal is printed */
    } else {
        printf("not equal\n");
    }
    return 0;
    

    }

Logical Operators


  • Purpose

    • Used to combine multiple boolean conditions to form more complex boolean expressions.


  • Categories

    • Binary Logical Operators: Operate on two boolean values.

      • AND

      • OR

      • XOR (Exclusive OR)

    • Unary Logical Operator: Operates on a single boolean value.

      • NOT


  • C Symbols for Logical Operators

    • &&: Logical AND

    • ||: Logical OR

    • !: Logical NOT


  • Truth Tables



    • AND Operator (a && b)



      • Evaluates to true only if both a is true AND b is true.

        a

        b

        a AND b


        false

        false

        false


        false

        true

        false


        true

        false

        false


        true

        true

        true

        • OR Operator (a || b)

        • Evaluates to true if a is true OR b is true OR both are true.

        a

        b

        a OR b


        :----

        :----

        :-------


        false

        false

        false


        false

        true

        true


        true

        false

        true


        true

        true

        true

        • XOR Operator (Exclusive OR)

        • Evaluates to true if either (a is true AND b is false) OR (a is false AND b is true). It is false if both are the same.

        • Note: There is no direct XOR operator symbol in C. It can be implemented using other logical operators (e.g., (a || b) && !(a && b) or a != b).

        a

        b

        a XOR b


        :----

        :----

        :--------


        false

        false

        false


        false

        true

        true


        true

        false

        true


        true

        true

        false

        • NOT Operator (!a)

        • Inverts the boolean value of a.

        a

        NOT a


        :----

        :------


        false

        true


        true

        false

        • Precedence of Logical Operators (Specific Hierarchy)

        • When multiple logical and comparison operators are used in an expression, their evaluation order is determined by precedence rules.

        • Logical NOT (!): Highest precedence among logical operators.

        • Size Comparators: <, >, <=, >=.

        • Equality Comparators: ==, !=.

        • Logical AND (&&).

        • Logical OR (||): Lowest precedence among logical operators.

        • For binary operators of equal precedence, evaluation typically occurs from left to right.

        • Recommendation: Use parentheses () to explicitly define the order of evaluation, especially for complex expressions, to improve readability and prevent unexpected results.

        Lazy Evaluation (Short-Circuiting) of Logical Operators

        • Concept

          • C utilizes lazy evaluation (also known as short-circuiting) for the binary logical operators && (AND) and || (OR).

          • Arguments are evaluated strictly from left to right.

          • If the outcome of the entire expression can be determined solely by evaluating the left argument, the right argument is not evaluated at all.

        • How it works

          • For && (AND):

            • If the left argument evaluates to false, the entire && expression must be false (since false && anything is always false). The right argument is skipped.

          • For || (OR):

            • If the left argument evaluates to true, the entire || expression must be true (since true || anything is always true). The right argument is skipped.

        • Examples of Lazy Evaluation (Assume a = 0 and b = 1)

          1. (a > 0) && (b < 2)

            • (a > 0) is (0 > 0), which is false.

            • Since the left argument is false, the && operator immediately returns false. The right argument (b < 2) is not evaluated.

          2. (a >= 0) && (b < 2)

            • (a >= 0) is (0 >= 0), which is true.

            • Since the left argument is true, the && operator needs to evaluate the right argument.

            • (b < 2) is (1 < 2), which is true.

            • Both are true, so && returns true.

          3. (a > 0) || (b < 2)

            • (a > 0) is (0 > 0), which is false.

            • Since the left argument is false, the || operator needs to evaluate the right argument.

            • (b < 2) is (1 < 2), which is true.

            • One is true, so || returns true.

          4. (a >= 0) || (b < 2)

            • (a >= 0) is (0 >= 0), which is true.

            • Since the left argument is true, the || operator immediately returns true. The right argument (b < 2) is not evaluated.

        Example: Logical Operators in C

        • Program to Check Divisibility

          /* Program to check if an integer is:
           * (i) divisible by 2 or by 3
           * (ii) divisible by 2 and by 3 */
          #include <stdio.h>
          
          int main(void) {
              int n;
              printf("Enter an integer:\n");
              scanf("%d", &n);
          /* (i) check if n is divisible by 2 or by 3 */
          if ((n % 2 == 0) || (n % 3 == 0)) {
              printf("divisible by 2 or by 3\n");
          } else {
              printf("not divisible by 2 or by 3\n");
          }
          
          /* (ii) check if n is divisible by 2 and by 3 */
          if ((n % 2 == 0) &amp;&amp; (n % 3 == 0)) {
              printf("divisible by 2 and by 3\n");
          } else {
              printf("not divisible by 2 and by 3\n");
          }
          return 0;
          

          }

        Operator Precedence (Comprehensive)

        • General Rules

          • Operators with higher precedence are evaluated before operators with lower precedence.

          • Operators with equal precedence are grouped together, and their evaluation order (associativity) is typically left-to-right, except for some specific cases (like unary operators and assignment).

        • Table of Operators (Descending Precedence)

          C Operator

          Meaning

          Associativity

          +

          unary plus

          right to left

          -

          unary minus

          right to left

          !

          unary logical NOT

          right to left

          *

          multiplication

          left to right

          /

          division

          left to right

          %

          modulo

          left to right

          +

          addition

          left to right

          -

          subtraction

          left to right

          <

          less than

          left to right

          <=

          less than or equal to

          left to right

          >

          greater than

          left to right

          >=

          greater than or equal to

          left to right

          ==

          equal to

          left to right

          !=

          not equal to

          left to right

          &&

          logical AND

          left to right

          ||

          logical OR

          left to right

          =

          assignment

          right to left

        The bool Type and Boolean Expressions in C

        • Traditional C (Prior to C11)

          • In earlier C standards, there was no dedicated boolean type.

          • Boolean expressions were evaluated as integers.

          • 0 (zero) was defined as false.

          • Any non-zero value was defined as true.

          • Thus, conditions could be:

            1. A boolean expression that explicitly evaluates to 0 (false) or 1 (true).

            2. Any expression that evaluates to 0 (false) or a non-zero value (true).

        • Examples of C's Integer Boolean Logic (Traditional C)

          int x = 0;
          int y = 1;
          int z = 2;
          
          if (x)      /* Condition (0) evaluates to false */
          { printf("This will not be executed.\n"); }
          
          if (!x)     /* Condition (!0, which is 1) evaluates to true */
          { printf("This will be executed.\n"); }
          
          if (y - z)  /* Condition (1 - 2 = -1) evaluates to true (non-zero) */
          { printf("This will be executed.\n"); }
          
          • c int b; /* an integer used as a boolean */ b = (1 > 2); /* (1 > 2) evaluates to false (0), b is assigned 0 */ if (b) /* Condition (0) evaluates to false */ { printf("This will not be executed.\n"); }

          • c int b; /* an integer used as a boolean */ b = (1 < 2); /* (1 < 2) evaluates to true (1), b is assigned 1 */ if (b) /* Condition (1) evaluates to true */ { printf("This will be executed.\n"); }

          • c int x = 0; int y = 2; int b; /* an integer used as a boolean */ b = (x && y); /* (0 && 2) evaluates to false (0), b is assigned 0 (lazy eval: y not checked) */ if (b) /* Condition (0) evaluates to false */ { printf("This will not be executed.\n"); }

          • c int x = 0; int y = 2; int b; /* an integer used as a boolean */ b = (x || y); /* (0 || 2) evaluates to true (1), b is assigned 1 (lazy eval: y is checked) */ if (b) /* Condition (1) evaluates to true */ { printf("This will be executed.\n"); }

        • C11 Standard: The bool Type

          • With the C11 standard, a dedicated boolean type, bool, is provided as an add-on in the <stdbool.h> library.

          • When <stdbool.h> is included:

            • bool becomes an alias for an integer type capable of storing boolean values.

            • true is defined as 1.

            • false is defined as 0.

          • Usage Example:

            #include <stdbool.h> // Include for bool type
            
            bool a = true;
            bool b = false;
            bool c = (1 < 2); // c is assigned true (1)
            
        • Example: Using bool from <stdbool.h>

          #include <stdio.h>
          #include <stdbool.h> // For the bool type
          #define THRESHOLD 10
          
          int main(void) {
              bool b; // A boolean variable
              int x;
              printf("Enter an integer:\n");
              scanf("%d", &x);
          // b evaluates to true if x is greater than THRESHOLD
          b = (x &gt; THRESHOLD);
          
          if (b) { // Condition (true or false)
              printf("x is greater than %d\n", THRESHOLD);
          }
          return 0;
          

          }

        Program to Find the Roots of a Quadratic Equation

        • Context

          • This problem is a classic example demonstrating the need for robust conditional logic to handle various cases.

          • The form of a quadratic equation is usually given as ax^2 + bx + c = 0. The roots are found using the quadratic formula: x = \frac{{-b \pm \sqrt{{b^2 - 4ac}}}}{{2a}}.

          • The nature and number of roots depend on the discriminant (the value under the square root): \Delta = b^2 - 4ac

            • If \Delta > 0: Two distinct real roots.

            • If \Delta = 0: One real root (a repeated root).

            • If \Delta < 0: Two complex roots.

          • Additionally, conditions like a=0 (making it a linear equation) must be handled.

        • Relevance

          • Such a problem will appear as a future practical question, requiring careful application of if...else if...else structures to cover all possible scenarios for a, b, c values and the discriminant.

        Test Yourself

        1. Given three variables declared as follows:

          int x = 0;
          int y = 10;
          int z = -40;
          

          What do the following boolean expressions evaluate to?

          • (i) x && z

          • (ii) !x && z

          • (iii) (x > y) && (y > z)

          • (iv) !(x > 2 * y) && z

          • (v) (y < 4) || (z < 0) && !x

        2. Given two boolean variables, a and b, write down a C boolean expression which evaluates to a XOR b.

          • Check that this works by writing a C program that uses the expression.