Table of Contents

Previous: 3. Basic Computer Operations

Next: 5. Problem Solving with Computers


4  Logic Operations

ALU of a computer CPU is responsible arithmetic operations and logical operations. Logical operators are connectors such as AND, OR, and NOT that connect statements. In this chapter, we will explain basic logical operators and truth table. Both propositions and predicates are discussed. Then, we explain how to express predicates in C programming language as logical expressions. In addition to logical expressions in C, we also introduce bitwise logical operations. Finally, language constructs of conditional statement in C, including if-then-else statements and switch-case are included in the chapter.

4.1  Propositions and Predicates

In the mathematical theory of a logical system, a proposition is in one of three forms: a primitive statement, a unary connector, and a number of binary connectors. A primitive statement is a statement describing a fact and is denoted by an English letter. For example, the followings are primitive logical statements:

Propositions:

Symbol

Primitive statement

p

John is a freshman major in IECS.

q

Mary lives in Taichung.

r

Helen went to a movie last night.

s

5 + 7 = 12.

However, a question, an exclamation, or a command is not a logical primitive statement. For example the following sentences are not primitive statements:

Does Mary live in Taichung?

When do you go to school every morning?

What a wonderful day!

Sit down and keep quiet.

The unary connector is the negation operator that transforms a statement p into Øp (NOT p). The binary connectors are conjunction, pÙq (p AND q), disjunction, pÚq (p OR q), exclusive or, pÅq (p XOR q),  implication, , pÞq (p implies q) and equivalence, pÛq (p if and only if q).

A primitive statement has a truth value of true or false, that is denoted as T or F, or as 1 or 0, respectively. The truth and falsity of the negation and other operations is given as the following truth tables:

p Øp

0

1

1

0

¡@

p q pÙq pÚq pÅq pÞq pÛq

0

0

1

1

0

1

0

1

0

0

0

1

0

1

1

1

0

1

1

0

1

1

0

1

1

0

0

1

Among these logical operators, negation (Ø) and disjunction (Ú) are sufficient to construct the other operators.

Logical operator

Equivalent expression

pÙq Ø(ØpÚØq)
pÅq Ø(ØpÚq)ÚØ(pÚØq)
pÞq ØpÚq
pÛq Ø(pÚq)ÚØ(ØpÚØq)

The above equivalent formulas can be proved by constructing the truth table of the expressions. We show verification of pÅq @ Ø(ØpÚq)ÚØ(pÚØq) and leave the other equivalent formulas as homework assignment.

p q Øp Øq pÅq

ØpÚq

Ø(ØpÚq)

pÚØq Ø(pÚØq) Ø(ØpÚq)ÚØ(pÚØq)

0

0

1

1

0

1

0

1

1

1

0

0

1

0

1

0

0

1

1

0

1

1

0

1

0

0

1

0

1

0

1

1

0

1

0

0

0

1

1

0

To express an English statement into logical expressions, a statement is split into a number of primitive statements and is denoted as an English letter. The followings are examples of primitive statements:

Symbol

Primitive statement

p

It rains.

q

John brings an umbrella.

r

John gets wet.

We show examples of translating English sentences to logical expression as below:

English sentence

Logical expression

If it rains and John brings an umbrella, John does not get wet.

(pÙq)ÞØr

If it rains and John does not bring an umbrella, John gets wet.

(pÙØq)Þr

A proposition can be generalized in two ways to construct a predicate:

  1. In a proposition, a primitive statement is replaced by an expression, e.g., x<y and x=sqrt(y), where sqrt() is the square root  function.
  2. The quantifier " meaning "for all" and $ meaning "there exists".

Examples of predicates are:

Predicates

x+y£z Ù x*y³z

"x"y: x=max(x,y) Ú y=max(x,y)

"x: x³0 Þ $y: y=sqrt(x)

4.2 Relations and Logical Expressions in C

Predicates in C programming language are called logical expressions or conditions. The primitive statements of conditions are either relations or Boolean functions. A relation is a comparison of two arithmetic expressions of the following syntax:

Relations:

equal to relation

expression1 == expression2

not equal to relation

expression1 != expression2

less than relation

expression1 < expression2

less than or equal to relation

expression1 <= expression2

greater than relation

expression1 > expression2

greater than or equal to relation

expression1 >= expression2

In C programming language, conditions are connected by operators negation (!), conjunction (&&), and disjunction (||).

Conditions:

negation

!condition

conjunction

condition1 && condition2

disjunction

condition1 || condition2

The truth values are represented as a number of any type. Value 0 is used to represent false and any value other than 0 is used to represent true. Often, value 1 is returned when a relation is evaluated to true. Program truthValues.c shows examples of conditions

1

2

3

4

5

6

7

8

9

10

#include <stdio.h>

int main(void) {
  printf("Truth: %d", 1==1);

  printf("False: %d", 1!=1);
  printf("Negation: %d\n", !(1==1));
  printf("Conjunction: %d\n", 1==1 && 1>0);
  printf("Disjunction: %d\n", 1!=1 || 1>0);

  return 0;
}

Truth: 1
False: 0

Negation: 0
Conjunction: 1
Disjunction: 1

Line 4 is the condition "one equals to one" that returns 1 for truth value true. Line 5 is the condition "one does not equal to one" that returns 0 for truth value false. Lines 6 to 8 illustrate conditions with negation, conjunction, and disjunction. 

4.3  Bitwise Operations in C

Another type of logical expressions in C is bitwise operations. A bitwise operator is a binary operator applying to corresponding bits of two expressions of the same length or applying to an expression and an integer. There are six bitwise operations defined in C programming language:

Bitwise operations

bitwise NOT

~expression

bitwise AND

expression1 & epxression2

bitwise OR

expression1 | epxression2

bitwise XOR

expression1 ^ epxression2

right shift

expression >> shift_value

left shift

expression << shift_value

The expressions in bitwise operations must be of fixed length. For example, 8-bit long for the characters, 16-bit long for short integers, and 32-bit long for integers. In addition, for bitwise AND, bitwise OR, and bitwise XOR, the two expressions must have identical bit length. For right shift and left shift operations, the shift value is an integer value.

Bitwise NOT reverses the value of each individual bit of its operand, i.e., it performs one's complement of the operand. For example, in program bitwiseNot.c, variable x is set to 8-bit hexadecimal value 4616=010001102, and then its negation is assigned to variable y in Line 6. We obtain the value of y to be 4616=0100 01102=1011 10012=B916.

1

2

3

4

5

6

7

8

9

#include <stdio.h>

  int main(void) {
  unsigned char x = 0x46, y;

  y = ~x;
  printf("x: %X, y: %X\n", x, y);

  return 0;
}

x: 46, y: B9

Bitwise AND, OR, and XOR perform AND, OR, and XOR operations on individual bits of the two operands. For example, program bitwiseAndOrXor.c shows the computational effect of bitwise AND, OR, and XOR operations. Variable x is initialized to hexadecimal value 4616. In Line 6, the value of variable y is set to xÙ0 that sets all bits of x to 0's. In Line 9, the value of variable y is set to xÚ255 that sets all bits of x to 1's. In line 12, the value of variable y is set to xÅ255 that reverses all bits of x as bitwise NOT does.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

#include <stdio.h>

int main(void) {
  unsigned char x = 0x46, y;

  y = x & 0x00;
  printf("Bitwise AND\tx: %02X, y: %02X\n", x, y);

  y = x | 0xFF;
  printf("Bitwise  OR\tx: %02X, y: %02X\n", x, y);

  y = x ^ 0xFF;
  printf("Bitwise XOR\tx: %02X, y: %02X\n", x, y);


  return 0;
}

A note about printf() in Lines 7, 10, and 14, the format specifier %02X will print two hexadecimal digits with leading zeros padded with 0. The output of program bitwiseAndOrXor.c is:

Bitwise AND  x: 46, y: 00
Bitwise  OR   x: 46, y: FF
Bitwise XOR  x: 46, y: B9

Bitwise shift is to shift the bits of its first operand to the right-hand-side or the left-hand-side a number of positions specified by its second operand. Program bitwiseShift.c illustrates the computation effect of bitwise shift operations. Variable x is initialized to hexadecimal value 4616. In Line 6, the value of variable y is set to x after shifting to the right-hand-side four positions and the four bits on the left-hand-side are filled in with 0's. In Line 9, the value of variable y is set to x after shifting to the left-hand-side four positions and the four bits on the right-hand-side are filled in with 0's.

1

2

3

4

5

6

7

8

9

10

11

12

#include <stdio.h>

int main(void) {
  unsigned char x = 0x46, y;

  y = x >> 4;
  printf("Bitwise right shift\tx: %02X, y: %02X\n", x, y);

  y = x << 4;
  printf("Bitwise left shift\tx: %02X, y: %02X\n", x, y);

  return 0;
}

The output of bitwiseShift.c shows that after right shift 4616 becomes 0416 and after left shift 4616 becomes 6016.

Bitwise right shift   x: 46, y: 04
Bitwise left shift     x: 46, y: 60

Bitwise logical operations are often used in programs to efficiently perform certain arithmetic operations. For example, left shift an integer one position is equivalent to the product of multiplying this integer by 2; right shift an integer one position is equivalent to the quotient of dividing this integer by 2. Conjunction operation of an integer with 1 is equivalent to the remainder of dividing this integer by 2. The general rules are:

  1. Left shift an integer k positions is equivalent to the product of multiplying this integer by 2k.

  2. Right shift an integer k positions is equivalent to the quotient of dividing this integer by 2k.

  3. Conjunction operation of an integer with 2k-1 is equivalent to the remainder of dividing this integer by 2k.

Program bitwiseArithmetic.c shows integer 3455 these operations with k=6, i.e., 2k=64. Line 6 show sthat x<<6 and x*64 have the same computational result. Line 8 shows that x>>6 and x/64 have the same computational result. Line 10 show that x&63 and x%64 have the same computational result

1

2

3

4

5

6

7

8

9

10

11

12

13

#include <stdio.h>


int main(void) {
 
unsigned int x = 3455;

  printf("x=%d, x<<6=%d, x*64=%d\n", x, x<<6, x*64);

  printf("x=%d, x>>6=%d, x/64=%d\n", x, x>>6, x/64);

  printf("x=%d, x&63=%d, x%%64=%d\n", x, x&63, x%64);

 
return 0;
}

The output of program bitwiseArithmetic.c is shown as below:

x=3455, x<<6=221120, x*64=221120
x=3455, x>>6=53, x/64=53
x=3455, x&63=63, x%64=63

For two positive integers, if the non-zero bits of binary representation of these two integers do not overlap, their sum can be implemented as a bitwise OR operation of the two integers. For example, bitwiseSum.c adds 50432 and 125 using bitwise OR. Variable x is set to 5043210=C50016 and variable y is set to 12510=7D16. Since the non-zero bits of x and y do not overlap, bitwise OR of the two numbers is equivalent to an addition.

1

2

3

4

5

6

7

8

9

10

11

12

#include <stdio.h>

int main(void) {
  int x=50432, y=125;
  int sum;

  sum = x | y;

  printf("x=%d, y=%d, sum=%d\n", x, y, sum);

  printf("x+y=%d\n", x+y);
  return 0;
}

x=50432, y=125, sum=50557
x+y=50557

We conclude precedence and precedence rules and associativity rules of all operators in C programming language, including, parentheses, arithmetic operators, logical operators, bitwise operators, assignments, and access operators. Access operators are [], ->, and . that will be explained in details in Section 5.2, Section 6.3, and Section 7.3. Operator question-colon (?:) is a conditional expression that is explained in the Section 4.4. Common (, is a punctuation used in variable declarations and for loops in Section 5.2. The precedence rules are in the order that operators in the first row have the highest precedence and operators in the last row have the lowest precedence. Note that +, -, and * in the second row are unary operators and  +, -, and * in the third and fourth rows are binary operators.

Operators

Associativity

()  []  ->  .

left to right

~  !  -  ++  --  +  -  *  &  (type)  sizeof

right to left

*  /  %

left to right

+  -

left to right

<<  >>

left to right

<  <=  >  >=

left to right

==  !=

left to right

&

left to right

^

left to right

|

left to right

&&

left to right

||

left to right

?:

right to left

=  +=  -=  *= /=  %=  &=  ^=  !=  <<= >>=

right to left

,

left to right

4.4  Conditional Statements in C

C programming language supports two kinds of conditional statements: if-then-else statements and switch-case statements.

if-then-else statements

 The syntax of  if-then-else statements have two forms:

if (condition) statement;

if (condition) statement1; else statement2;

A statement in the then clause and else clause can be a block statement, i.e., a sequence of statements enclosed by a pair of curly brackets { }. The first syntactic form is also called an if-then statement. That is, the else clause in this case is the null statement. The semantics of if-then statements and if-then-else statement is:

  1. if-then statement: if condition is evaluated to true (i.e., to a value other than 0), then statement in the then clause is executed; otherwise the if-then statement does nothing.

  2. if-then-else statement: if condition is evaluated to true (i.e., to a value other than 0), then statement1 in the then clause is executed; otherwise, statement2 in the else clause is executed.

When execution of either the then clause or the else clause is completed, program execution continues to the next statement following the conditional statement.

Computational effect of a program can be specified using the operational semantics. We use a semi-formal notation to specify programs. In program specification (in orange color), we use an italicized  name to denote a mathematical function and the left arrow (¬) to represent assignment of a value to a variable. For example, assigning the absolution value of variable x to itself is expressed as:

x ¬ abs(x)

where abs() is the mathematical function (in purple color) of absolute value. Function abs() can be defined as below:

abs(x) º if x<0 then -x else x

If we substitute the definition of abs(x)in the above program specification, it is rewritten to:

if x<0 then x ¬ -x else x ¬ x

The new specification can be implemented as an if-then-else statement as below:

if (x<0) x = -x;

else x = x;

Since the assignment statement x = x in the else clause does not have any computational effect, it can be omitted and the program is simplified to the if-then statement as below:

if (x<0) x = -x;

The above program shows a conditional statement without else clause. However, if we change the program specification to:

  y ¬ abs(x)

º if x<0 then y ¬ -x else y ¬ x

The program must be written with if-then-else because assignment y = x cannot be omitted in this case.

if (x<0) y = -x;

else y = x;

Note that the condition must be enclosed by a pair of parentheses. In this example, the then and else clauses have only one statement, they are not enclosed by a pair of curly brackets. We will consider another example computing maximum value of two variables. Let max() be a function that returns the maximum value of two variables and be defined as below:

max(x,y) º if x³y then x else y

Suppose the program specification is to assign the maximum value of x and y to variable ans:

  ans ¬ max(x,y)

º if x³y then ans ¬ x else ans ¬ y

The program that implements this specification is written as:

if (x>=y) ans = x;

else ans = y;

Ternary statements

C programming language provides a ternary operation (an operation with three operands) ?: to write a conditional statement in a brief way. Its syntax is:

condition ? expression1 : expression2

The semantics of the ternary expression is that if condition is evaluated to true (values other than 0) then expression1 is executed; otherwise, expression2  is executed. With this ternary operation, we can rewrite the program computing maximum value to:

ans = (x>=y) ? x : y;

Nested conditional statements

Conditional statements can be enclosed in another conditional statements, i.e., they can be written as a nested conditional statements. For example, a possible form is to enclose two inner if-then-else statements in the then clause and the else clause of the outer if-then-else statement:

if (condition1) {

  if (condition2) {

    statement1;

  }

  else {

    statement2;

  }

}

else {

  if (condition3) {

    statement3;

  }

  else {

    statement4;

  }

}

When writing a compound statement, it is important to place indentation in the program text so it is easier to read. Let consider function max3() which returns the maximum value of three variables x, y, and z. Function max3() can be defined as below:

  max3(x,y,z)

º if (x³y)

  then if (x³z) then x else z

  else if (y³z) then y else z

If we expand the definition of function max3() in program specification ans¬max3() is written to the following formula:

  ans ¬ max3(x,y,z)

º if (x³y)

  then if (x³z) then ans ¬ x else ans ¬ z

  else if (y³z) then ans ¬ y else ans ¬ z

The program specification can be implemented as the following C program:

if (x>=y) {

  if (x>=z) ans = x;

  else ans = z;

}

else {

  if (y>=z) ans = y;

  else anz = z;

}

Another form of if-then-else statements which is frequently used in C programs is called else-if construct:

if (condition1)

  statement1;

else if (condition2)

  statement2;

else if (condition3)

  statement3;

else statement4;

If we rewrite the else-if construct with proper indentation, the exact syntax is:

if (condition1)

  statement1;

else

  if (condition2)

    statement2;

  else

    if (condition3)

      statement3;

    else

     statement4;

We modify function max3() to the following definition:

  max3(x,y,z)

º if (x³y Ù x³z) then x

  else if (x<y Ù y³z) then y

  else z

and the program specification to:

  ans ¬ max3(x,y,z)

º if (x³y Ù x³z) then ans ¬ x

  else if (x<y Ù y³z) then ans ¬ y

  else ans ¬ z

The program is then implemented using the else-if construct:

if (x>=y && x>=z) ans = x;

else if (x<y && y>=z) ans = y;

else ans = z;

switch-case statements

In C programming language, switch-case statements are used to implement a special form of the else-if construct when all of the conditions are equality tests of a given variable on a finite cases (usually a small number of cases) of discrete values. The syntax of switch-case statements is:

switch (variable) {

  case value1: statement1;

  case value2: statement2;

  case value3: statement3;

  case value4: statement4;

  default:    statement5;

}

The number of case lines can be as many as needed and the default line is optional. The semantics of a switch-case statement is the value of variable is compared with values from the first to the last case in the specified order. Once one value of the case clause is matched, all statements following the case clause are executed unless a break statement is encountered. A switch-case statement is complete if the execution reaches a break statement or the last statement.

For example, program print_numbers_no_break.c uses a switch-case statement to print numbers of one, two, three, and four. Lines 9 to 15 make up a switch-case statement. Since the value of x is set to 2, statements starting from case 2: are executed. Hence, two, three, four, and others are output because there is not break statement after any case..

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

#include <stdio.h>

int main(void) {

  int x;

  printf("Enter an integer: ");
  scanf("%d", &x);

  switch (x) {
    case 1: printf("one\n");
    case 2: printf("two\n");
    case 3: printf("three\n");
    case 4: printf("four\n");
    default: printf("others\n");
  }
  return 0;
}

Enter an integer: 2
two

three

four

others

If only one number is going to be output, a break statement must be added after every printf() as in program print_number_with_break.c below: When x is set to 2, only two is output.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

#include <stdio.h>

int main(void) {

  int x;

  printf("Enter an integer: ");
  scanf("%d", &x);
¡@

  switch (x) {
    case 1:
{

      printf("one\n");

     break;

    }
    case 2:
{

      printf("two\n");

     break;

    }

    case 3: {

      printf("three\n");

     break;

    }

    case 4: {

      printf("four\n");

     break;

    }

    default: printf("others\n");

  }

  return 0;
}

Enter an integer: 2
two

In fact, the switch-case statement from Line 9 to 27 can be written with the else-if construct as below:

if (x==1) printf("one\n");

else if (x==2) printf("two\n");

else if (x==3) printf("three\n");

else if (x==4) printf("four\n");

else printf("others\n");

In a switch-case statement, multiple values can be placed in consecutive case clauses without specific order. For example, if x is set to 12, the following program segment will output value two:

switch (x) {

  case 11:
  case  1:
{

    printf("one\n");

    break;

  }
  case  2:

  case 12: {

    printf("two\n");

    break;

  }

  case 13:

  case  3: {

    printf("three\n");

    break;

  }

  case 4: {

    printf("four\n");

    break;

  }

  default: printf("others\n");

}


Table of Contents

Previous: 3. Basic Computer Operations

Next: 5. Problem Solving with Computers