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 4040 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, xx and yy:

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

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

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

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

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

      • Less Than or Equal: is xx less than or equal to yy? (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.10.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, xx and yy, 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., 1e101e-10 or 1e121e-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=0a = 0 and b=1b = 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 ax2+bx+c=0ax^2 + bx + c = 0. The roots are found using the quadratic formula: x=b±b24ac2ax = \frac{{-b \pm \sqrt{{b^2 - 4ac}}}}{{2a}}.

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

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

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

            • If \Delta < 0: Two complex roots.

          • Additionally, conditions like a=0a=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 aa, bb, cc 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.