Chapter 5
Chapter 5
The numbers are shown as a fraction times some power of ten where the exponent is shown after
the E. The rst number is 234.0, the second is .00987, the third is -34.5.
When we represent numbers using this system, we do not need to store the decimal point (it
is always in the same place) or the base (it is always E, standing for 10). So, of our 5 digits, let
us say that we use three digits for the fractional part and two digits for the exponent. One digit
of the fractional part and one digit of the exponent is reserved for the sign. This leaves only two
digits for the absolute value of the fractional part, and it leaves one digit for the absolute value of
the exponent. Thus, the range of values for the fractional part is ;:99 to +:99 and the range for
exponents is ;9 to +9.
Even though the range of actual values is quite large (we can represent numbers from almost
negative one billion to positive one billion), there are only two signicant digits of precision all
other digits will be zeros contributed by the power of ten. So, the range of numbers is from
;990 000 000 to +990 000 000 (-.99E+9 to +.99E+9). With this scheme, it would be impossible
to represent a number such as 123.4567 exactly. The best we can do is represent it as +.12E+3,
which is the number 120 | not nearly as accurate as 123.4567. We have a loss of precision (or
accuracy) because of the limited number of digits we have for representing oating point numbers.
There is a slight distinction between precisions and accuracy. In the above representation scheme,
we can always say there are 2 digits of precision however, the accuracy depends on the value of
the exponent. The smallest number we can represent is .00000000099 (+.99E-9), which is pretty
darn accurate. However, if the exponent is +9, our accuracy is only 5 million. If more digits are
used to represent oating point numbers, the precision and the range can be greater. For example,
if 6 digits were allowed, with four digits for a signed fractional part, we could represent 123.4567
as +.123E3, which is 123.0. If 7 digits were allowed, with 5 digits for a signed fractional part, we
could represent the same number as +.1234E3, which is 123.4, and so forth.
Conceptually, binary representation of numbers is no dierent from decimal representation.
The nite size imposes a limit on the range of integers and on the precision and range of oating
point numbers. Binary representation is also tailored to facilitate the basic operations in hardware,
such as addition and subtraction. For example, as we saw in Chapter 1, integers are typically
represented in what is called the two's complement number system. However, one does not need
to know the number system to realize that the limits on the range of values will be similar in
nature and will depend on the sizes used to represent the numbers.
Recall that, in a computer, memory is organized as a sequence of bytes, each byte with an
address, and storage is allocated in units of bytes. For example, if 1 byte is used for signed integers,
5.1. REPRESENTING NUMBERS 209
the range of values (in decimal) is -128 to 127 and unsigned integers have the range 0 to 255. If
2 bytes are used to represent signed integers, the range is -32768 to +32767 and 0 to 65535 for
unsigned integers. If 4 bytes are used to represent integers, the range will be appropriately greater.
Similarly for oating point numbers with 4 bytes to represent oating point numbers, the precision
is equivalent to about 7 signicant decimal digits and a magnitude between approximately 10E38
and 10E-38. If more bytes are used for oating point numbers, the precision and the range are
both appropriately greater.
So far we have used char, int, and float data types in our programs. Character data type is
usually encoded as an ASCII integer value (signed or unsigned) in one byte of memory. Integers
are at least two bytes in size, and oating point numbers are at least four bytes in size. C provides
additional integer sizes and oating point data types that provide greater range and/or precision.
The data type of a constant, written directly into a program, is ascertained from the way it is
written. Integer constants are written as a string of digits, optionally preceded by a unary positive
210 CHAPTER 5. NUMERIC DATA TYPES AND EXPRESSION EVALUATION
or a negative operator. Commas are not allowed. Decimal integer constants should be written
without leading zeros, for example:
29
-173
0
1
-525
+7890
Alternate number systems may also be used to express integer constants in C programs. Octal
numbers are written with a leading zero, and hexadecimal numbers are written with a preceding
zero followed by the letter x or X:
A constant to be represented as a long int may be explicitly written using the sux l or L, as
in:
123L
45678l
Any integer constant that is too big to t into the integer size is interpreted by the compiler as
long.
Unsigned integers can be of all sizes, int, long, and short. The range of unsigned integers
is 0 through 2 ;1 , where k is the number of bits, so for 16 bits the maximum unsigned integer is
k
0xFFFFU
123U
0777u
12345678UL
0X8FFF FFFFLU
5.1. REPRESENTING NUMBERS 211
5.1.2 Single and Double Precision Floating Point Numbers
Dierent sizes of oating point data can also be declared with the keywords float and double.
The type specier double is used to declare double precision oating point numbers. The size of
float is typically 32 bits, and that of double is 64 bits. For greater precision, most scientic and
engineering computation should be performed using the double data type. Furthermore, extra
precision may be provided for oating point numbers by declaring them long double. (This may
be the same as or more bits of precision as double, depending on implementation). Here are
example declarations for oating point numbers:
float x
double GPR
long double y
Decimal float constants in programs have an integer part and a fractional part with a decimal
point between them. They may also be written in scientic (or exponential) notation, i.e. a decimal
number multiplied by a power of ten to indicate the actual position of the decimal point. Positive
and negative numbers may be written with an explicit positive or negative unary operator.
123.789
0.5534
+9635.0000
-8942.3214
-0.765E5
1.4523e12
0.786345e-10
The last three numbers are written in exponential notation with the exponent of ten shown after
the letter e or E. The exponent may be a positive or a negative integer. For clarity, always write
float numbers with at least one digit before and one after the decimal point for example, zero
is 0.0 in float representation.
Floating point constants are taken to be of double precision type by default. Single precision
oating point constants may be specied with a sux f or F.
34.567f
3.141516F
23456789.171819L
212 CHAPTER 5. NUMERIC DATA TYPES AND EXPRESSION EVALUATION
5.2 New Control Constructs
So far, we have seen all of the basic control constructs of the C language for calling functions,
branching, and looping. In this section we introduce two new looping constructs that can be used
in place of while namely for loops and do...while loops.
The keyword, for, and the parentheses are required as shown. Notice the three expressions are
separated by semi-colons (
). The semantics of the for statement is as follows. The expression,
<expr1>, is evaluated once before the loop condition is tested for the rst time <expr2> is the loop
condition which is evaluated prior to each execution of the loop body and <expr3> is evaluated
at the end of the loop body, just prior to testing the condition. The process repeats until the loop
condition becomes False. The body of the loop is <statement>, which, as usual, may be any valid
type of C statement empty, simple, or compound. As with the while loop, if the loop condition
evaluates to True, the loop body is executed otherwise, if the loop condition evaluates to False,
the loop is terminated, and control passes to the next statement following the for statement. In
typical use, the expressions, <expr1> and <expr3> initialize and update a variable, respectively.
Figure 5.1 shows the control ow for a for statement.
A for statement includes all the necessary features of a loop: an initialization expression, a
loop condition, and an update expression. Thus, the following two forms of implementing a loop
are equivalent:
<expr1>
while (<expr2>) f
<statement> and for (<expr1> <expr2> <expr3>) <statement>
<expr3>
g
The break and continue statements can also be used in the body of a for statement, just as
in a while statement. The use of a for statement or a while statement to implement a loop is a
matter of choice, based on the logic of the algorithm. One advantage might be that writing a for
statement reminds one that initialization and update expressions are usually necessary for a loop.
5.2. NEW CONTROL CONSTRUCTS 213
?
expr1
P?PPP False
-PPP expr2 PP
PPP
expr3 True
6 ?
statement
?
Figure 5.1: Control Flow of for Loop
An Example: Factorial
Let us consider an example task which may require a bigger range of integers than the one provided
by int on many machines. The task is to determine a cumulative product from 1 to a positive
integer, n. The product from 1 to n is called the factorial of n, written n!. The algorithm is very
simple: read an integer n call a function fact(n) which returns the factorial of n print the result.
The function fact() merely needs to multiply a cumulative product variable, initialized to 1,
by all integers from 1 through n:
initialize product to 1
repeat for values of i = 1, 2, 3,..., n
product = product * i
return product
The variable, product, must be initialized to 1 before the loop, otherwise the cumulative product
will be garbage. Each iteration brings us closer to the result. We will use a for statement to
implement the iterative algorithm for a factorial function as shown in Figure 5.2. The for loop
executes as follows. The rst expression in parentheses is an initialization expression, i.e. i is
initialized to 1. The second expression is the loop condition. If the second expression, i <= n,
evaluates to True, then the loop body is executed. The third expression is the update expression
it is evaluated after the loop body is executed, and control then passes to the loop condition. In
our example, the expression, i = i + 1, is evaluated to update the variable, i, after the loop body
214 CHAPTER 5. NUMERIC DATA TYPES AND EXPRESSION EVALUATION
/* File: fact.c
Program computes the factorial of integers using function
fact().
*/
#include <stdio.h>
int fact(int n)
main()
{ int n
printf("***Factorial Program***\n")
printf("Type positive integers, EOF to terminate\n")
while (scanf("%d", &n) != EOF)
if (n <= 0)
printf("%d typed, type a positive integer\n", n)
else
printf("Factorial of %d is %d\n", n, fact(n))
}
product = 1
for (i = 1
i <= n
i = i + 1)
product = product * i
return product
}
where scanf() reads an integer item if possible and stores it in n. The value returned by scanf()
is then compared with EOF and if the value returned is NOT EOF, the loop executes. As soon as
scanf() returns EOF, the loop is terminated. The while expression serves both to read an item
and to check if the returned value is EOF. The loop body tests the value of n if it not a positive
integer, the user is asked to retype a positive number otherwise, the value of fact(n) is printed.
Here is a sample session run on an IBM PC:
5.2. NEW CONTROL CONSTRUCTS 215
***Factorial Program***
Type positive integers, EOF to terminate
4
Factorial of 4 is 24
5
Factorial of 5 is 120
-3
Negative number -3 typed, type positive integers
6
Factorial of 6 is 720
7
Factorial of 7 is 5040
8
Factorial of 8 is -25216
^Z
The cumulative product in the factorial function grows very fast with n. For moderately large
values of n, the cumulative product overows the int type object the number is too large for the
size of the object. When this occurs, the results are meaningless. Usually, an overow is indicated
when a program, working correctly for smaller numbers, gives ridiculous results for larger numbers.
In the case of the factorial function, the rst sign of trouble is a negative result for the factorial of
8. We know the result must be positive since we are multiplying only positive numbers. What has
happened is the result has overowed into the sign bit resulting in a negative integer. If factorial
of larger numbers is desired, a long int variable should be used for the variable product as well
as for the function fact. Here is a revised version of the factorial function.
/* Function computes a long factorial of n using a for loop. */
long longfact(int n)
{ long int product
int i
product = 1
for (i = 1
i <= n
i = i + 1)
product = product * i
return product
}
We must keep several things in mind when using the function, longfact(), in the driver program.
In the calling function, if the value returned by longfact() is saved, it must be assigned to a long
integer otherwise, a long result would be converted to int by dropping higher order bits and the
result would be meaningless. In addition, to print the long value of longfact(), the conversion
specier must be qualied by the prex l:
printf("Factorial of %d is %ld\n", n, longfact(n))
216 CHAPTER 5. NUMERIC DATA TYPES AND EXPRESSION EVALUATION
?
- statement
P?PPP
PPPexpression
P PP
True PP
False
?
Figure 5.3: Control Flow of do...while Loop
do
<statement>
while (<expression>)
Figure 5.3 shows the control ow for this construct. As with the other loop constructs, the
break and continue statements can also be used with the do...while statement. The choice of
a loop construct depends on the program logic. There are situations when one construct may be
preferable to another.
5.2. NEW CONTROL CONSTRUCTS 217
An Example: Square Root
Programs are often written to nd a solution (or solutions) to an algebraic equation for example:
y2 ; x = 0
Here, the solution for the variable, y, is the square root of x. In general, such solutions are real
numbers, and as we have seen, oating point representations of real numbers use a nite number
of bits, and are therefore limited in the precision of the result. Solutions to most numeric problems
can never be exact (all solutions are precise only up to a certain number of decimal digits) but
the result may be suciently close to the real solution to be acceptable.
One important numeric computation method to nd solutions to equations involves successive
approximations. This method starts with a guess for the solution to the problem, and tests if
the guess satises the equation. If the guess is close enough for a solution, it is accepted and
computation terminates otherwise, the guess is improved, i.e. brought closer to the solution and
the process is repeated. After each iteration, the guess is closer and closer to the solution, until it
is acceptably close enough.
One successive approximation algorithm we will use is Newton's method to compute the square
root of a number, x. Newton's method starts with an arbitrary guess, and if it is not good enough,
it is improved by averaging the guess with x=guess. The process continues until the guess is close
enough. Here is an example of the process for square root of 9.0:
In just three iterations, we have arrived close to the square root of 9.0 (which is 3.0). We will say
a guess is close enough to the solution, if x and the square of guess dier by a small value, say
0.001, or less. The algorithm is simple:
We will start with an arbitrary guess, say 1.0, for the square root of the number, x. In a loop,
each iteration improves the guess of the square root of x until the guess is close enough. In our
implementation, we assume two functions: one to test if a guess is close enough, and the second
to improve the guess. This algorithm works for any successive approximation method the only
dierence would be how to improve the guess, and how to check the guess for closeness to the
solution. Here is the code fragment for square root using a do...while statement:
218 CHAPTER 5. NUMERIC DATA TYPES AND EXPRESSION EVALUATION
guess = 1.0
do
guess = improve(guess, x)
while (!close(guess, x))
The body of the loop follows the keyword do. The loop body is executed and then the while
expression is tested for True or False. If it is True, the loop is repeated otherwise, the loop is
terminated. The above loop body calls on a function improve() to improve the guess and the
condition is then tested to see if the improved guess is close enough by the function close().
As we said, the dierence between do...while and the other loop constructs is that in this
case the loop is executed at least once while loops and for loops may be executed zero times if
the loop condition is initially False. In the case of successive approximations, we always expect
the initial guess to need improvement so, the loop must be executed at least once.
Figure 5.4 shows the implementation of the driver. The source le includes a header le
mathutil.h that declares the function prototypes for close(), improve(), and other functions
dened in a source le, mathutil.c, shown in Figure 5.5. The two source les sqroot.c and
mathutil.c must be compiled and linked to create an executable le. Here is mathutil.h:
/* File: mathutil.h */
/* File contains prototypes for functions defined in mathutil.c */
double improve(double guess, double x)
int close(double guess, double x)
double absolute(double x)
Notice we have used the type, double for the parameters and return values of the functions because
precision is important in successive approximation algorithms. It is best to use double precision in
all such computations. We have also included the header le, tfdef.h, which denes the symbolic
constants TRUE and FALSE.
The program driver uses a loop to read a positive, double precision number into x using the
conversion specication %lf. (When a double precision number is printed, conversion specication
is still %f since a printed double precision oating point number looks the same as a single precision
number). If the number read into x is negative or zero, a message is printed and the loop is repeated
until a positive number is read. We have used the do...while construct here, since we know that
the loop must be executed at least once to get the desired data.
Next, guess is initialized to 1.0 and the loop body improves guess We have included a debug
statement to print the value of the improved guess during program testing. The loop repeats until
guess is close enough to be an acceptable solution.
We still need to write the functions improve() and close(). The function close() tests if the
absolute value of the dierence between the square of guess and x is small enough. We will use a
function, absolute(), that returns the absolute value of its argument. Figure 5.5 shows close()
and absolute() in the source le, mathutil.c. Some of the functions dened in this source le
5.2. NEW CONTROL CONSTRUCTS 219
/* File: sqroot.c
Other Files: mathutil.c
Header Files: tfdef.h, mathutil.h
Program computes and prints square roots of numbers. Uses Newton's
method to compute square root of x: Start with any guess. Test if
it is acceptable. If not, improve guess by averaging it with x/guess.
*/
#include <stdio.h>
#include "tfdef.h"
#include "mathutil.h"
#define DEBUG
main()
{ int i
double x, guess
/* File: mathutil.c */
#include <stdio.h>
#include "tfdef.h"
#include "mathutil.h"
/* Tests if square of guess approximately equals x. */
int close(double guess, double x)
{
if (absolute(guess * guess - x) < 0.001)
return TRUE
else
return FALSE
}
Type a number: 16
guess = 8.500000
guess = 5.191176
guess = 4.136665
guess = 4.002257
guess = 4.000000
Sq.Rt. of 16.000000 is 4.000000
The debug statement shows how guess is changed at each step. Once we are satised with the
program, we can remove the denition of DEBUG.
Next, we modify our program to encapsulate it into a function, sqroot(), and to provide user
control over the precision desired for the solution instead of building it into the function, close().
The sqroot() function requires two arguments, a number and an acceptable error in the solution.
We also require a new function close2() that checks if a given guess is close enough to a solution
with a specied margin of error. With this modication, it is not necessary to use double for
numbers in main(). Only the actual computations need to be double type for greater precision.
Figure 5.6 shows the revised driver in which float numbers are used in main() and the function
sqroot() is called to nd the square root. Figure 5.7 shows the prototypes added to mathutil.h
and the new functions in mathutil.c. The driver simply repeats the following loop: read a
number if the number is negative, continue the loop otherwise, call sqroot() to nd the square
root of the number within specied margin print the value. The function sqroot() merely starts
with a guess and improves it in a loop until it is within an allowable margin of error. The nal
acceptable guess is returned. The function close2() tests if a guess is close to the solution within
a specied error.
In main(), numbers are read into float variables, so when arguments are passed to sqroot(),
they are cast to double. Likewise, the returned double value is cast to float before assigning it
to the variable root. Here is the statement that uses cast operators to convert types:
root = (float) sqroot((double) x, 0.001)
Recall that a oating point constant is always assumed to be of type double. If function prototypes
are declared, we don't have to convert the types explicitly by cast operators, the compiler will take
care of that for both the arguments and the returned value. However, the explicit cast operators
improve readability by showing that conversions are taking place.
Sample Session:
222 CHAPTER 5. NUMERIC DATA TYPES AND EXPRESSION EVALUATION
/* File: sqrt2.c
Other Files: mathutil.c
Header Files: tfdef.h, mathutil.h
Program computes and prints square roots of numbers until the end of
file. Uses Newton's method to compute the square root of x to within a
specified error margin.
*/
#include <stdio.h>
#include "tfdef.h"
#include "mathutil.h"
main()
{ int i
float x, root
do
guess = improve(guess, y)
/* improve guess. */
while (!close2(guess, y, error))
/* while guess not close */
return guess
/* when close enough, return guess.*/
}
The last example shows the square root of 25.0 to be slightly dierent from the correct value of 5.0,
but within our allowed error of 0.001. It must be remembered that oating point representation
cannot be exact due to the nite number of bits used. Therefore, if the error specied were very
small, it may not be possible to arrive at an answer with the desired accuracy. That is, the guess
may never converge to a value such that close2() returns True and the loop in sqroot() would
never terminate. In successive approximations algorithms, one must guard against possible lack
of convergence such as by putting a limit on the number of loop iterations allowed.
In Chapter 6 we will see that standard library functions are available to compute the square
root and the absolute value of a number. Our emphasis here has been to illustrate program devel-
opment using just the basics of a programming language, viz. expressions including assignments,
branching, and looping.
void printmsg(void)
main()
{
/* print a message */
printmsg()
}
No parameters are required for the function, printmsg(), and it returns no value it merely prints
its message. In the function call in main(), parentheses must be used without any arguments.
Observe that no return statement is present in printmsg(). When a function is called, the body is
executed and, when the end of the body is reached, program control returns to the calling function.
Such a return from a called function without a return statement is often called returning by falling
o the end. There are times when it is necessary to return from a void function before the end
of the body. In such case, a return statement, with an empty expression may be used to return
nothing:
void printmsg(void)
{
printf("****HOME IS WHERE THE HEART IS****\n")
return
}
A return statement can also be used elsewhere in the body to return control immediately to
the calling function. Consider a function which prints the values of its arguments if they are all
positive otherwise it does nothing:
void func(int x, in y)
226 CHAPTER 5. NUMERIC DATA TYPES AND EXPRESSION EVALUATION
{
if (x <= 0 || y <= 0)
return
printf("x = %d, y = %d\n", x, y)
}
If either of the arguments is not positive, the function returns to the calling function. If it does
not return, then it prints the values of the arguments.
The use of void for a function returning no value is not strictly necessary. We could declare
the function as being type int (or any other type) and simply not return any value and never use
the value of the function in an expression. However, the void declaration makes the nature of
the function explicit to someone reading the code and may allow the compiler to generate more
ecient object code.
5.3.2 Enumeration
The data type, enum (for enumeration) also allows improvement in program clarity by specifying
a list of names, the enumeration constants, which are associated with constant integer values. It
is similar to using #define directives to dene constant integer values for a set of symbolic names
however, with enum the compiler can generate the values for you, and may check for proper use of
enum type variables. A variable of enum type is declared as follows:
The variable, flag, is dened here to be of a type which can take on the two enumerated constant
values, FALSE and TRUE. Normally, enumeration constants are identiers whose values start at zero
and increase in sequence: here, FALSE is 0, and TRUE is 1. However, the enumeration can have
explicit constant values specied in the enumeration:
Here, SUN is associated with value 1, and the rest of the names have values in increasing sequence:
MON is 2, TUE is 3, and so on until SAT which is 7. The variable, day can hold any of the enumerated
values.
An enumeration type can be given a tag, i.e a name which can be used later to declare variables
of that tagged enumeration type. For example, we can name an enumeration:
where the name boolean can then be used to declare variables of that enumeration type:
5.3. SCALAR DATA TYPES 227
enum boolean flag1, flag2
This declaration denes variables, flag1 and flag2, which are of a type specied by the boolean
enumeration that is, flag1 and flag2 can have values FALSE or TRUE. It is also possible to specify
a tag and declare variables in the same declaration:
The rst declaration species a tag, boolean, for the enumeration as well as declaring a variable,
done of this type. The second declaration denes a variable, found, of the enumeration boolean
type. Here is a function, digitp(), that returns a boolean value to the calling function. (The
calling function must also declare the enumeration in order to use the returned value correctly).
Remember, the value of an enum type variable is an integer. An enumerated data type is
primarily a convenience for writing the source code information about the symbolic names are
not retained at run time. For example, if we were to execute a statement:
it would print
digitp returns 1
NOT
However, some symbolic debuggers may use the enumerated names for displaying debugging in-
formation.
228 CHAPTER 5. NUMERIC DATA TYPES AND EXPRESSION EVALUATION
5.3.3 Dening User Types: typedef
The C language provides a facility for dening synonyms for data types to make programs more
readable. New data types that are equivalent to existing data types may be created using the
typedef declaration. The syntax is:
The variable yrs can have age type values. In this case, the primary dierence is that we can
have more meaningful names for data types than the generic name int.
A typedef denition is also commonly used to \hide the details" of more complicated decla-
rations:
typedef enum { FALSE, TRUE } boolean
boolean flag
The type denition denes data type, boolean which is a synonym for an enumerated type con-
sisting of two constant values FALSE and TRUE. Variables of type boolean can now be dened, and
they can be assigned one of the enumerated values. In fact, the name, boolean, can be used like
any other data type. Functions can have boolean parameters and can return boolean values. For
example, we could write:
flag = TRUE
if (flag)
printf("Flag is true\n")
Let us consider the task of a simple calculator. It should read two numbers and then read an
operator that is to be applied to the operands. The operator should be applied to the operands
and the result printed. (When an operator appears after the operands, the expression is said to
be in postx form). The algorithm for a postx calculator is:
repeat until end of file or error in reading numbers
read two numbers and an operator
apply operator to the numbers and get result
print result
5.4. OPERATORS AND EXPRESSION EVALUATION 229
The program must make sure that two valid numbers and an operator are read correctly. We
will ensure that two numbers are read correctly by examining the value returned by scanf(). The
buer will then be scanned and ushed until a valid operator is found. The program is shown in
Figure 5.8.
The while loop continues until scanf() is unable to read two numbers. If scanf() reads two
numbers, it returns a value of 2, and the loop is executed. In the loop, we use get operator() to
get a valid operator. The function, get operator() will scan each new character in the keyboard
buer until an acceptable operator is found. Once an operator is read, an error ag of type
boolean is initialized to FALSE.
A switch statement is used to determine the result of applying the operator to the operands.
The division operator can lead to trouble if oprnd2 is zero divide by zero is a fatal error and the
program would be aborted. We trap this error by testing for a zero value of oprnd2, in which case
we set error to TRUE. If there is no error, the result is printed otherwise, an error message is
printed. The loop repeats until scanf() does not read 2 floats (including detecting EOF).
The function get operator() consists of a loop that continues to read a character until a valid
operator is read skipping over any white space and any erroneous characters. It uses a boolean
type function, operatorp(), to test if an argument is an acceptable operator. Figure 5.9 shows
the required functions.
Sample Session:
***Postfix Calculator***
We have purposely used a lot of white space to show that the calculator functions correctly.
/* File: calc.c
This program is a postfix calculator. Two operands followed by an
operator must be entered. The program prints the result. The program
repeats until end of file.
*/
#include <stdio.h>
typedef enum { FALSE, TRUE } boolean
char get_operator(void)
boolean operatorp(char c)
main()
{ float oprnd1, oprnd2, result
char c
boolean error
printf("***Postfix Calculator***\n\n")
printf("Type two numbers, followed by an operator: +, -, *, or /\n")
printf("EOF to quit\n")
switch(c) {
case '+': result = oprnd1 + oprnd2
break
case '-': result = oprnd1 - oprnd2
break
case '*': result = oprnd1 * oprnd2
break
case '/': if (oprnd2)
result = oprnd1 / oprnd2
else
error = TRUE
break
}
if (error == FALSE)
printf("%f %c %f = %f\n", oprnd1, c, oprnd2, result)
else
printf("Runtime error: %f %c %f\n", oprnd1, c, oprnd2)
} /* end of while loop */
} /* end of program */
int x = 10, y = 7, z = 20
In the rst expression, if x > 0 is False, there is no need to evaluate the second part of the logical
AND expression since the AND operation will be False. Similarly, in the second expression, the
logical OR expression is True if the rst part, x > 0, is True there is no need to evaluate the
second part. C evaluates only those parts of a logical expression that are required in order to
arrive at the result of the expression.
When in doubt as to the order of evaluation within an expression, parentheses may be used to
ensure evaluation is performed as intended.
int n = 3, m = 2
long large
float x = 9.0, y = 5.0
double z = 4.0
As with the precedence and associativity rules, when in doubt as to the type and/or preci-
sion of an expression evaluation, cast operators may be used to force conversions to the desired
type. Remember, only values of variables are converted for the purpose of computation, NOT the
variables themselves.
236 CHAPTER 5. NUMERIC DATA TYPES AND EXPRESSION EVALUATION
5.4.3 Some New Operators
In Table 5.1 there are several operators which we have not yet discussed. Some of these are
described below the remainder will be delayed until later chapters when we discuss the appropriate
data types.
x = 4
y = 4
The value of
++x - x++
is implementation dependent. A compiler may either evaluate the rst term rst or the second
term rst. It is therefore not possible to say what the expression will evaluate to. For example,
assume that x is initially 1. If the rst expression is evaluated rst, then the expression is:
2 - 2
i.e. 0, and x is 3. On the other hand, if the second term is evaluated rst, then the expression is:
3 - 1
i.e. 2, and x is 3.
Increment and decrement operations can just as well be written as assignment expressions:
x = x + 1
y = y - 1
The use of increment and decrement operators does not accomplish anything that cannot be done
by appropriately placed assignments. These operators were designed to be used with machines
that have increment and decrement registers in which case the compiler can take advantage of
these registers and improve the performance of the program. However, many machines today do
not have these registers, so most compilers translate expressions with increment and decrement
operators in exactly the same manner as they do assignment expressions, but these operators
remain as a \shorthand" syntax for compact programs.
The syntax of the increment and decrement operators is:
++<Lvalue>
--<Lvalue>
<Lvalue> ++
<Lvalue> --
The operand must be and <Lvalue>, i.e. a location into which a value can be placed. (So far, we
have seen that only a variable name may be used as an <Lvalue>. We will see other possibilities
238 CHAPTER 5. NUMERIC DATA TYPES AND EXPRESSION EVALUATION
Composite Equivalent
x += 5
x = x + 5
y -= 12
y = y - 12
x *= 3
x = x * 3
y /= 5
y = y / 5
x %= 7
x = x % 7
in Chapter 6). The precedence and associativity of increment and decrement operators is given in
Table 5.1. Here are some examples of their use in program code:
for (i = 0
i < MAX
i++) The message, This is a test will be printed MAX
printf("This is a test\n")
times.
n = 0
The expression n++ evaluates to the value of n
while (n++ < 10) before it is incremented. The loop will print the
printf("Value of n is %d\n", n)
values 1,2, : : : ,10 for n.
where <op> may be one of the binary arithmetic operators, +, -, *, /, or %. The left operand of
these operators must be an <Lvalue>, but the right operand may be an arbitrary <expression>.
Again, there is no particular advantage in using the composite assignment operators over the
simple assignment operator except that they produce a somewhat more compact program. The
precedence and grouping for composite assignment operators given in Table 5.1 shows they are
the same as the assignment operator. Figure 5.10 shows the factorial function (see Figure 5.2)
using these new operators.
5.4. OPERATORS AND EXPRESSION EVALUATION 239
prod = 1
/* initialize */
for (i = 1
i <= n
i++) /* loop from 1 to n */
prod *= i
/* compute cumulative product */
return prod
/* return product */
}
Another way of stating this in words is that z should be assigned the value of y if x < y or x,
otherwise. The (operator) symbols ? and : may be used to form such a conditional expression as
follows:
z = x < y ? y : x
The expression to the right of the assignment operator is evaluated rst as follows. If x < y, the
expression evaluates to the value of the expression after ?, i.e. y. Otherwise, it evaluates to the
value of the expression after :, i.e. x. In other words, the expression evaluates to the larger of x
and y which is then assigned to the variable, z.
As another example, we can write an expression that evaluates to the absolute value of x:
x < 0 ? -x : x
Each number of the sequence is computed by adding the previous two numbers of the sequence.
Thus, we must start with the rst two numbers, which are both 1, then the next number is
1 + 1 = 2, the next one is 1 + 2 = 3, the next one is 2 + 3 = 5, and so on.
We will write a driver, main(), which calls a function, fib(), to print the Fibonacci numbers.
The function starts with two variables, which are initialized to the values of the rst two numbers
1 and 1. Each new number is computed as a sum of the previous two until the limit is reached.
Figure 5.12 shows the code.
5.4. OPERATORS AND EXPRESSION EVALUATION 241
/* File: fib.c
Program computes and prints Fibonacci numbers less than a
specified limit of 100.
*/
#include <stdio.h>
#define LIM 100
void fib(int lim)
main()
{
printf("***Fibonacci Numbers***\n")
printf("Limit is %d \n", LIM)
fib(LIM)
}
/* Function computes and prints the Fibonacci numbers less than lim. */
void fib(int lim)
{ int i, j, n
if (n < lim)
printf("%d\n", n)
/* print the next fib. number */
}
}
***Fibonacci Numbers***
Limit is 100
1
1
2
3
5
8
13
21
34
55
89
sizeof <expression>
The unary operator, sizeof, yields the size, in bytes, of the type of its operand. The operand
may be an arbitrary expression, however, the expression is NOT evaluated the sizeof expression
simply evaluates to the number of bytes used for the type of the result. For example, the expression,
sizeof x, evaluates to the size of x in bytes. Here is a code fragment using the sizeof operator:
5.4. OPERATORS AND EXPRESSION EVALUATION 243
The rst printf() statement will print the size (in bytes) of the int type object, x. The second
will print the size of the value of the expression, x+y. As we saw earlier, this addition would be
done in double precision and the result would be a double. Remember, the expression, x+y is not
evaluated only its size is used by the sizeof operator. Also remember that sizeof is an operator,
like + not a function call. It has a precedence and associativity like any other operator (shown in
Table 5.1). That is why the parentheses are required in that second printf(), the precedence of
sizeof is higher than +. Without the parentheses, the expression would be evaluated as:
(sizeof x) + y
It is also possible for the operand of sizeof to be a parenthesized type name, like a cast operator,
rather than a variable name, for example:
sizeof (int)
sizeof (float)
sizeof (long int)
sizeof (unsigned long int)
We can easily write a program to determine the sizes of dierent types for the host implemen-
tation. The code is shown in Figure 5.13. A sample output for the HP9000 is:
***Sizeof operator***
244 CHAPTER 5. NUMERIC DATA TYPES AND EXPRESSION EVALUATION
/* File: size.c */
main()
{ int x
double y
printf("***Sizeof operator***\n\n")
printf("Size of x is %d bytes\n", sizeof x)
printf("Size of x+y is %d bytes\n\n", sizeof (x+y))
printf("Size of data types in bytes:\n")
printf("Size of int type is %d\n", sizeof(int))
printf("Size of long int is %d\n", sizeof(long int))
printf("Size of short int is %d\n", sizeof(short int))
printf("Size of unsigned int is %d\n", sizeof(unsigned int))
printf("Size of float is %d\n", sizeof(float))
printf("Size of double is %d\n", sizeof(double))
}
Whenever the size of a type is required in a program, the sizeof operator should be used
rather than the actual size, since the actual value is implementation dependent. Such a use of
the sizeof operator in a program ensures that the program will be portable from one type of
computer to another.
x = 3.0
printf("Truncated Square of %f = %d\n", x, trunc_square(x))
}
int trunc_square(float z)
{
return (int) (z * z)
}
The function trunc square() returns integer type and main() uses the default declaration
for trunc square(). The float argument, x in the function call in main() is converted to
double. But trunc square() declares a float formal parameter, z. An attempt will be
made to access a double object as a float. The function may not access the correct value
passed as an argument. Thus, it is always best to use function prototypes to avoid confusion.
3. An expression is written without consideration of precedence and associativity of the oper-
ators. For example,
while (x = scanf("%d", &n) != EOF)
...
Wrong! The scanf() value is compared rst with EOF and the result of the comparison is
assigned to x. Using parentheses:
while ((x = scanf("%d", &n)) != EOF)
...
xis assigned the value returned by scanf(), and the value of x is then compared with EOF.
Examples where associativity must be considered include:
a = 10
b = 5
c = 20
d = 4
a - b - c is -15
a / b / c / d is 0
a % d % b % c is 2
4. Increment and decrement operators are used incorrectly. Remember that postx implies
increment/decrement after evaluation and prex implies increment/decrement before evalu-
ation.
246 CHAPTER 5. NUMERIC DATA TYPES AND EXPRESSION EVALUATION
5.6 Summary
In this chapter we have tied up some loose ends and formalized some of the concepts from previous
chapters. We have seen how the nite number of bits available to represent numbers limits the
range and precision of the numbers stored in the computer. We have introduced additional data
types which can extend the range and increase precision as needed for some applications. We
have discussed the data types void (when no value is expected) and enum (for improving program
readability). We have also shown how user dened names for data types can be dened using
typedef with syntax:
We have extended our available control constructs by introducing two variations on the looping
constructs provided in the language: the for statement and the do...while statement, with
syntax:
<expr1>
while (<expr2>) f
for (<expr1> <expr2> <expr3>) <statement> equivalent to <statement>
<expr3>
g
and
do
<statement>
while (<expression>)
We have also described how expressions are evaluated, including the determination of the
type of the result and the order of applying operators, giving the full precedence and associa-
tivity table for all C operators (Table 5.1). We have described some new operators, such as the
increment/decrement operators:
++<Lvalue>
--<Lvalue>
<Lvalue> ++
<Lvalue> --
<expression1> , <expression2>
sizeof <expression>
Other operators in the table such as the indirection, array subscripting, structure accessing, and
bitwise operators will be described in later chapters.
248 CHAPTER 5. NUMERIC DATA TYPES AND EXPRESSION EVALUATION
5.7 Exercises
1. If x is 100 and z is 200, what is the output of the following:
if (z = x)
printf("z = %d, x = %d\n", z, x)
x + a / b
x + (int) a / b
x = 10 y= 20 z = 30
x = z / y + y
x = x / y / z
x = x % y % z
x = w / y + y
u = z / y + y
u = w / y + y
u = x / y / w + u / v
5.7. EXERCISES 249
5. What is the output of the following program?
#define PRHAPS
#define TWICEZ z + z
main()
{ int w, x, y, z
float a, b, c
w = 16
x = 5
y = 15
z = 8
a = 1.0
b = 2.0
c = 4.0
#ifdef PRHAPS
x = 15
y = 5
#endif
printf("(a). %d %d\n", x, y)
printf("(b). %d\n", TWICEZ * 2)
printf("(c). %f %f\n", w / z * a + c, z / w * b + c)
printf("(d). %d\n", z % y % x)
}
SWAP(x1, x2)
printf("x1 = %d, x2 = %d\n", x1, x2)
}
(b) #define SWAP(x, y) {int temp
temp = x
x = y
y = temp
}
main()
{ int x1 = 10, x2 = 20
SWAP(x1, x2)
printf("x1 = %d, x2 = %d\n", x1, x2)
}
(c) #define SWAP(x, y) int temp
temp = x
x = y
y = temp
main()
{ int x1 = 10, x2 = 20
printf("Swapping Values\n")
SWAP(x1, x2)
printf("x1 = %d, x2 = %d\n", x1, x2)
}
250 CHAPTER 5. NUMERIC DATA TYPES AND EXPRESSION EVALUATION
7. Write a while and a do...while loop to read and echo long integers until end of le. Allow
for the possibility that the rst input is an end of le.
8. Write a for loop to print out squares of integers in the sequence 5, 10, 15, 20, 25, etc. until
100.
9. Given the following declarations:
int x = 100, y
What are the values of x and y after each of the following expressions is evaluated (the
expressions are evaluated in sequence)?
y = x++
y = ++x
y = --x
y = x--
10. What are the values of the following expressions considered sequentially:
x = 100
y = 200
y = y++ - ++x
y = ++y - x++
y = ++y * 2
y = 2 * x++
of x, where n is an integer. Use a function, fact(), to compute the factorial. Write a driver
that reads input values of x, and nds exp(x). Use as many terms as needed to make values
before and after an additional term very close.
3. Write a function to evaluate sin(x) using the expansion shown below. Use it in a program
to nd the sine of values read until end of le.
sin(x) = x1! ; x3! + x5! ; x7! + x9! ;
1 3 5 7 9
4. Write a function, cos(x), using the expansion below and use it in a program to nd the
cosine of values read until EOF.
x2
x4
x6
x 8
cos(x) = 1 ; 2! + 4! ; 6! + 8! ;
5. What are the limitations on the accuracy of the above expansions?
6. Write a function that returns the number of ways that r items can be taken together out of
n items. The value of combination is:
comb(n r) = (n ; nr!)! r!
Use long integers for factorials.
7. Extend the range of possible values for Problem 6 by cancelling out common factors in
numerator and denominator.
252 CHAPTER 5. NUMERIC DATA TYPES AND EXPRESSION EVALUATION
8. Write a program that uses Newton's method to nd the roots of the equation:
f (x) = x2 + 5 x + 6 = 0
Newton's method uses successive approximations. Start with a guess value for root. The
improved value of root is given by:
newroot = root ; ff0((root
root)
)
where f (root) is the value of the function when x equals root, and f 0(root) is the value of
the function below when x equals root:
f 0(x) = 2 x + 5
9. Write a program that nds the approximate value of an integral of a function whose four
sample values s1, s2, s3, s4 are specied at time instants t1, t1 + h, t1 + 2 h, t1 + 3 h.
The user should be asked for the value of the interval size, h, and starting instant, t1. The
approximate value of an integral from t1 to t1 + 4 h is the sum of the area under each
rectangle made up of the sample value and the inter-sample distance, i.e.:
s1 h + s2 h + s3 h + s4 h
10. Write a program that reads in the coecients and the right hand side values for two linear
simultaneous equations. Solve the equations for the unknowns and print the solution values.
The equations are:
a(1 1) x1 + a(1 2) x2 = c1
a(2 1) x1 + a(2 2) x2 = c2
where a(1 1), a(1 2), c1, a(2 1), a(2 2), and c2 are the coecients to be read, and x1 and x2
are the unknowns. To solve the equations, multiply the rst equation coecients and right
hand side by ; a(2 1)
a(1 1)
and add the corresponding values to those of the second equation. The
new, modied value of a(2 1) will be zero, so the second equation can be solved for x2, and,
19. If the GCD of two numbers, m and n is 1, they have no common divisor. Write a program
to nd all pairs of numbers, in the range 2 to 20, that have no common divisors. (Refer to
Problem 3.12 for the denition of GCD).
20. A rational number is maintained as a ratio of two integers, e.g. 20/23, 35/46, etc. Rational
number arithmetic adds, subtracts, multiplies and divides two rational numbers. Write a
program that repeatedly reads and adds two rational numbers. The program should print
the result in each case as a rational number.
21. Write a function to subtract two rational numbers.
22. Write a function to multiply two rational numbers.
23. Write a function to divide two rational numbers.
24. Write a function to reduce a rational number. A reduced rational number is one in which
all common factors in the numerator and the denominator have been cancelled out. For
example, 20/30 is reduce to 2/3, 24/18 is reduced to 4/3, and so forth. The GCD can be
used to reduce a rational number.
25. Modify the rational numbers programs in Problems 20 through 24 so the result is rst
reduced before it is printed.
254 CHAPTER 5. NUMERIC DATA TYPES AND EXPRESSION EVALUATION