Tuesday, 1 October 2013

Decision and Branching Concepts

Boolean Variables and Data Type ( or lack thereof in C )

  • A true boolean data type could be used for storing logical values, and would only have two legal values - "true", and "false".
  • C does not have boolean data types, and normally uses integers for boolean testing.
    • Zero is used to represent false, and One is used to represent true.
    • For interpretation, Zero is interpreted as false and anything non-zero is interpreted as true.
    • To make life easier, C Programmers typically define the terms "true" and "false" to have values 1 and 0 respectively.
      • In the old days, this was done using #define:
        • #define true 1
        • #define false 0
      • Today it is better to use const int instead of #define:
        • const int true = 1;
        • const int false = 0;
      • C99 provides a new header, stdbool.h, that defines some new helpful types and constants:
        • The type "bool" is the same as a newly defined type "_Bool"
          • _Bool is an unsigned integer, that can only be assigned the values 0 or 1
          • Attempting to store anything else in a _Bool stores a 1. ( Recall that C interprets any non-zero as true, i.e. 1 )
          • Variables can now be declared of type "bool".
        • stdbool.h also defines constants "true" and "false", with value 1 and 0 respectively.
        • Use these new features using #include <stdbool.h> at the top of your program

Relational Operators

  • Relational operators are binary operators that evaluate the truthhood or falsehood of a relationship between two arguments, and produce a value of true ( 1 ) or false ( 0 ) as a result.
  • The following table shows the 6 relational operators and their meaning, by example:
Operator
Meaning
A > B
True ( 1 ) if A is greater than B, false ( 0 ) otherwise
A < B
True ( 1 ) if A is less than B, false ( 0 ) otherwise
A >= B
True ( 1 ) if A is greater than or equal to B, false ( 0 ) otherwise
A <= B
True ( 1 ) if A is less than or equal to B, false ( 0 ) otherwise
A = = B
True ( 1 ) if A is exactly equal to B, false ( 0 ) otherwise
A != B
True ( 1 ) if A is not equal to B, false ( 0 ) otherwise
  • ( Note: There should not be a space between the two equals signs in = =. I am adding one in these HTML notes so the two signs do not run together, as ==. )
  • DANGER DANGER: Do not confuse the equality test operator, = =, with the assignment operator, =. It is a VERY common error to use the wrong number of equals signs, ( either too many or too few ), and the resulting code will usually compile and run but produce incorrect ( and often unpredictable ) results.
    • Example: "while( i = 5 )" is an infinite loop, because i is given the value 5 by the assignment, and since this is non-zero, it is always true. The programmer probably meant to use "while( i = = 5 )", which will test whether or not i is equal to 5, and will only continue to loop as long as this is true.
  • <, <=, >, and >= have equal precedence, higher than = = and !=, which have equal precedence with each other. All these operators are evaluated left to right.
  • Note: As a general rule, floating point numbers ( e.g. floats or doubles ) should NOT be tested for exact equality, because roundoff makes it virtually impossible for two doubles to ever be exactly equal. Instead one checks to see if the two numbers are acceptable close to each other:
    • double x, y, tolerance = 1.0E-6;
    • BAD: if( x = = y )
    • GOOD: if( fabs( x - y ) < tolerance )
    • This is particularly important when testing the stopping condition of an iterative algorithm, because otherwise the roundoff error could cause the program to run forever. WRONG: while( estimatedError != 0.0 )

Logical Operators

  • Logical operators are used to combine and evaluate boolean expressions, generally in combination with the relational operators listed above.
  • The following table shows the 3 logical operators and their meaning, by example:
Operator
Meaning
A && B
A AND B - True ( 1 ) if A is true AND B is also true, false ( 0 ) otherwise
A | | B
A OR B - True ( 1 ) if A is true OR B is true, false ( 0 ) otherwise
! A
NOT A - True ( 1 ) if A is false ( 0 ), false ( 0 ) if A is true ( non-zero ).
  • Note: There should not be a space between the | characters in | |, but one is being added here for clarity.
  • DANGER DANGER: If you only use a single & or | instead of the double && or | |, then you get valid code that is beyond the scope of this course, but which will not do what you want.
  • ! has a very high precedence, higher than the relational operators listed above. && and | | have lower precedence than the relational operators, with && having higher precedence than | |.
  • Example: To determine whether x is between 5.0 and 10.0:
    • INCORRECT: 5.0 < x < 10.0
    • CORRECT: 5.0 < x && x < 10.0
    • Question - What is the value of the incorrect expression ( regardless of the value of x ), and why?
  • Example: Leap year occurs when the current year is divisible by 4, but NOT when the year is divisible by 100, UNLESS the year is also divisible by 400.
    int year;
    bool leap_year;
    
    leap_year =  ( ( ( ( year % 4 ) == 0 ) && ( ( year % 100 ) != 0  ) ) || ( ( year % 400 ) == 0 ) ); 
    
    // Or equivalently:
    
    leap_year = !( year % 4 ) && year % 100 || !( year % 400 );
  • Short Circuiting of Logical Operations:
    • In a complex logical expression, the computer will stop evaluating the expression as soon as the result is known.
    • "A && B" will not evaluate B if A is already false.
    • "A | | B" will not evaluate B if A is already true.
    • Example: "if( x != 0.0 && 1.0 / x < 10.0 )" will never divide by zero.

if-else

  • "if" blocks and "if-else" blocks are used to make decisions, and to optionally execute certain code.
  • The general syntax of the if-else structure is as follows:
            if( condition )
                true_block
            else
                false_block 
  • Either the true_block or the false_block can be either a single statement or a block of statements enclosed in { curly braces }
  • The else clause and the false block are optional.
  • In execution, the condition is evaluated for truth or falsehood.
    • If the condition evaluates to true ( non-zero ), then the true_block of code is executed.
    • If the condition evaluates to false ( zero ), then the false-block of code is executed if it is present.
    • After one or the other block is executed, then execution continues with whatever code follows the if-else construct, without executing the other block.
  • Example:
            if( x < 0.0 ) {
                printf( "Error - The x coordinate cannot be negative. ( x = %f ) \n", x );
                exit( -1 );
            }
  • Example:
  •     
  • Either the true block or the false block can contain any other code, including more if-else statements.
    • When an if-else construct is used in the true block of another if-else, that is termed nested ifs.
    •       
          
    • When an if-else is used in the false block of another if-else, that is termed a sequential if, ( or cascaded if ) :
    • 
            
           
  • if-else blocks should normally be indented, as shown above. Beware, however, that this is only for the benefit of the human, and that the computer does not pay attention to the indentation. In the following example, for instance, the exit( ) call is NOT under the control of the if, and will execute every time:
  •     if( x < 0.0 )
           printf( "Error - The x coordinate cannot be negative. ( x = %f ) \n", x );
           exit( -1 );
  • When there are lots of if-elses in close proximity, particularly if they are nested, then the use of extra braces can help to show the true logic desired. Labeling the ( closing ) braces can help humans to better understand the logic.
  • Exercise: Write a program to solve the quadratic equation for A x^2 + B x + C = 0. Check for both real and imaginary roots, and for the special cases when A = 0, and/or B = 0, and/or C = 0.

switch

  • In many cases,  an integer valued expression must be compared against a number of possible choices, such as the following:
        const int left = 1, right = 2, up = 3, down = 4; // For improved readability   
        int direction;
        . . .
        printf( "Please enter a direction, 1 for left, 2 for right, ... " );
        scanf( "%d", &direction );
    
        if( direction = = left )
            x--;
        else if ( direction = = right )
            x++;
        else if ( direction = = up )
            y++;
        else if ( direction = = down )
            y--;
        else
            printf( "Error - Invalid direction entered.\n" );
  • In this situation a much better solution is the switch statement.  The equivalent switch for the above is:
    const int left = 1, right = 2, up = 3, down = 4; // For improved readability   
    int direction;
    . . .
    printf( "Please enter a direction, 1 for left, 2 for right, ... " );
    scanf( "%d", &direction );
    
    switch( direction ) {
    
         case left:
              x--;
              break;
    
         case right:
               x++;
               break;
    
         case up:
              y++; 
              break;
    
         case down:
              y--;
              break;
    
         default:
         	  printf( "Error - Invalid direction entered.\n" );
              break;
    
    } /* End of switch on direction */
  • The general syntax for a switch statement is:
    switch( variable_integer_expression ) {
    case constant_integer_expression_1 :
    code block 1;
    break; // ( Optional - See below )
    // Repeat the case sub-construct as many times as needed
    default: // Optional
    default code block;
    break; // Not needed. Added for defensive programming only.
    } // End of switch on . . .
  • The variable expression is compared against each of the constant expressions one by one. As soon as a match is found ( equality test is true ), execution jumps to that location and begins executing the statements found there.
  • Execution continues until a break statement is encountered.
    • If the break statement is omitted from the end of a case, then execution will continue into the next case, and so on until a break is eventually encountered, or until the end of the switch is reached.
    • Omitting the break statement is a common mistake.
    • Sometimes the break is omitted intentionally, in order to combine cases:
    bool done = false;  // Assumes stdbool.h has been #included
    char choice;
    
    while ( ! done ) {
    
        // Code to print out a menu and ask for choice omitted
    
        scanf( "%c", &choice );
    
        switch( choice ) {
    
            case 'E':
            case 'e':
                /* Code here to handle Entering data */
                break;
    
            case 'C':
            case 'c':
                /* Code here to handle Calculations */
                break;
    
            case 'H':
            case 'h':
            case '?':
                /* Code here to deliver Help */
                break;
    
            case 'Q':
            case 'q':
                done = true;
                break;
    
            default:
                printf( "Error - Invalid choice: %c\n", choice );
                printf( "Choose 'H' for help.\n";
                break;
    
        } /* End of switch on menu choices */
    
    } /* End of infinite while loop */
    
  • The default case is a catch-all, and will be executed if none of the above cases match the variable expression.  Although the default case is not required, it is good programming practice to always include a default case, in order to catch unexpected or even "impossible" situations.
  • Where execution speed is critical, the most likely case should be placed first, followed by the next most likely, and so on.  Where execution speed is not critical, or where all cases are equally likely, then cases should be placed in a logical order.  There are obvious exceptions to these rules of thumb, such as when one case is designed to fall through to a following case.
  • The last case ( or default if present ) does not technically need a "break", but it is good defensive programming to add one anyway, so that it doesn't get overlooked if additional cases get added at a later time.

The Conditional Operator

Earlier we looked at unary and binary operators under C.  In addition, the C language contains one ternary operator, known as the conditional operator, ?:
  • The conditional operator functions very much like an if-else construct, and the two constructs can often be used interchangeably.
  • However, due to its operator status, the results of the conditional operator can also be used as part of a larger expression and/or be assigned to a variable.
  • The syntax of the conditional operator is:
condition ?  true_clause : false_clause
  • The condition is first evaluated, ( along with any side effects. )  Then, either the true clause or the false clause will be executed, ( along with any side effects ), depending on whether the condition evaluates to true ( non-zero ) or false ( zero ) respectively.  The value of the conditional operator as used in a larger expression is the value of whichever clause gets executed.
  • Examples:
    max = i > j ? i : j;
    abs = x > 0 ? x : -x;
    printf(  "X is %s.\n", ( x < 0 ? "negative" : "non-negative" ) );
    // Parentheses needed in the last example because +  and * higher than > and ?:
    pay = base + ( sales > quota ? 1.5 : 1.0 ) * bonus;
    
  • Exercises:  Implement the following using the conditional operator:
        if( error < tolerance )
            step *=  2.0;
        else
            step =  1.0;

        if( x != 0.0 )
            z /= x;
        else
            z =  infinite;

        if( x > 0.0 )
            sign =  1.0;
        else if ( x = = 0.0 )
                sign =  0.0;
        else sign = -1.0;

Related Topics

The following topics are not covered here, but may be found in many books on C/C++;
  • Bitwise logical operators, | and &, ^, and ~
  • Bit shift operators, <<, >>
  • Combinations of these with assignment, e.g. |=, &=, <<=, >>=, and ^=.

No comments:

Post a Comment