Controlling the flow of your program
5.3 The block IF construct
We can now return to the basic block IF construct which was informally intro-duced in Section 5.1, and examine its structure in more detail. The initial statement of the construct is a block IF statement which consists of the word IF followed by a logical expression enclosed in parentheses, followed by the word THEN:
IF (logicaCexpression) THEN
This is followed by a sequence, or block, of statements which will be executed only if the logical expression is true. The block of statements is terminated by an ELSE IF statement, an ELSE statement or an END IF statement.
The ELSE IF statement has a very similar syntax to that of an IF statement:
ELSE IF (logicaCexpression) THEN
It is followed by a block of statements which will be executed if the logical expression is true, and if the logical expression in the initial IF statement of the block construct, and those of any preceding ELSE IF statements are false. The block of statements is terminated by another ELSE IF statement, an ELSE statement or an END IF statement.
The ELSE statement simply consists of the single word ELSE and introduces a final block of statements which will be executed only if the logical expressions in all preceding IF and ELSE IF statements are false.
The construct is always ended by an END IF statement.
There are no restrictions upon what types of statements may appear within a block of statements other than that any multi-statement constructs, such as further block IF constructs, or the CASE and DOconstructs that we shall meet later, must be wholly contained within a single block. It is obvious that no other situation would make any sense!
A block IF construct is, therefore, always introduced by a block IF statement and terminated by anEND IF statement. There may be any number of ELSE IF statements, each followed by a block of statements, or there may be none.
138 Controlling the flow of your program
IF (logical expression) THEN block of Fortran statements ELSE IF (logical expression) THEN
block of Fortran statements ELSE IF (logical expression) THEN
ELSE
block of Fortran statements END IF
Figure 5.9 The block IF structure.
There may be one ELSE statement followed by a block of statements, or there may be none; if there is an ELSE statement then it, and its succeeding block of statements, must follow allELSE
IF
blocks. This structure is shown in Figure 5.9.[I]
ProblemExample 4. I calculated the number of bags of wheat that were required to sow a triangular field. Modify this program to deal with the situation in which an exact number of full bags is required in a more aesthetically pleasing manner (and one which is easier to follow).
rn
AnalysisIn Example 4.1 we added 0.9 to the result of dividing the quantity of seed required by 10000 (to calculate the number of multiples of10kilos required). This used the truncation mechanism to specify an extra bag (which will only be partially used) if the true quantity is not an exact multiple of 10kilos. A better way would be to use a block IF. Since we have already fully analysed this problem in Chapter 4 we shall not repeat the data design, but will merely show a revised structure plan:
The block IF construct 139
We can find out if any more is needed by testing if the amount required is greater than the amount in the bags.
!II
SolutionPROGRAM wheat_sowing IMPLICIT NONE
"
A program to calculate the quantity of wheat required to sow a triangular field
! Variable declarations
REAL:: a,b,c,s,area,density,quantity INTEGER :: num_bags
! Read the lengths of the sides of the field
PRINT *,"Type the lengths of the three sides of the field &
&in metres: "
READ *,a,b,c
! Calculate the area of the field s = 0.5* (a+b+c)
area = SQRT(s*(s-a)*(s-b)*(s-c))
! Read sowing density
PRINT *, "What is the sowing density (gm/sq.m.)? "
READ *,density
! Calculate quantity of wheat in grams and the number of
! full 10 kg bags quantity = density*area
num_bags = O.OOOl*quantity ! Any part-full bag is excluded
! Check to see if another bag is required IF (quantity> 10000*num_bags) THEN
num_bags = num_bags+l END IF
! Print results
PRINT *,"The area of the field is ",area," sq. metres"
PRINT *," and ",num_bags ," 10 kilo bags will be required"
END PROGRAM wheat_sowing
140 Controlling the flow of your program
Multiply25.39 by17.25 to six significant figures:
25.39 x 17.25 2539 17773
5078 12695 4379775
Answer is437.978
Figure 5.10 Rounding errors in hand calculations.
There are two important points to note here. The first is that the relational expres-sion is comparing a real value (quantity) with an integer one (lOOOO*num_baqs). In this case the expression is evaluated as if comparing the difference between the two operands with zero; thus the expression
quantity> 10000*num_baqs is evaluated as if it were
(quantity-10000*num_baqs) > 0.0
To do this, 10000*num_baqs is converted to its real equivalent and then 'the real subtraction is performed.
The second point concerns the accuracy of real arithmetic. Real numbers are stored in the computer as an approximation to a defined degree of accuracy, and therefore when such numbers are used in arithmetic expressions the least significant digits may get lost as a result of round-off. Figure 5.10 illustrates this in the context of hand calculation to six digits of accuracy, where the product of two four-digit numbers requires seven digits to be accurate; the answer is therefore expressed as a six digit number after rounding the sixth digit. The normal rule is that if the first digit to be omitted (the seventh in this case) is in the range 0-4 then it (and any subsequent ones) are simply dropped, but if it is in the range 5-9 (as in this example) then the last significant digit is increased by one (from 7to 8 in this case) before the remainder are dropped.
A computer operates in exactly the same way and therefore any real arithmetic operation is liable to introduce such a rounding error. Frequently this is of no consequence as the computer is working to a greater accuracy than required for the problem. However there are four cases where it does matter a great deal.
One of these is where a large amount of numerical calculation is being carried out and in this case a higher level of accuracy (or precision) can be specified, as we shall see in Chapter 10.The second case was mentioned in Section 3.3 and relates to
The block IF construct 141 the situation when a large integer value is converted to its real equivalent, with a
consequent loss of precision. The third case is the related conversion problem in which a real number is to be truncated before being stored as an integer. The final case is more interesting, and concerns the situation in which we wish to compare or subtract two real numbers which are almost exactly the same. We can illustrate the last two situations by reference to the program we have just written.
Let us suppose that the sides of the field are 130 m, 100 m and 130 m, and that the sowing density is25 g/m2• A few moments' calculation shows that the area of the field is 6000 m2, and hence that 150 kg of seed are required. num_bags should therefore be 15 and the test should find that these contain exactly enough seed. In practice, though, it probably won't be like that. For example, the calculation of the area could lead to a value such as 5999.999999 (to 10 significant figures) or to 6000.000001. The subsequent calculation of the quantity of seed will give further possible rounding errors leading to a (real) value for O.OOOl*quantity of perhaps 14.99999999 or 15.00000001.
Although for all practical purposes these two values are the same as the true value of 15, when they are truncated to calculate num_bags they will lead to integer values of 14 and 15 respectively. In the first case quantity will clearly be less than 10000*num_bags and so the situation will be compensated for. In the second case, however, it is possible that quantity is fractionally more than 150000.0 (for instance 150000.000 1) and that the relational expression will be true, leading to a calculation of 16 bags!
We can deal with this by never testing whether two real values are equal (which is essentially what we are doing here in the borderline case) but rather by testing whether their difference is acceptably small. In this case, therefore, we could say that since the numbers being compared are of the order of 100 000 (actually 150 000 in this example) and since any errors in calculation will, hopefully, be much less than 1%, we should alter the test to read
IF (quantity> 10000*num_bags+1000) THEN num_bags = num_bags + 1
END IF
A better way might be to avoid any reference to num_bags and to express the test as follows:
IF (O.OOOl*quantity - INT(O.OOOl*quantity) > 0.1) THEN num_bags = num_bags + 1
END IF
thereby eliminating multiplying quantity by 0.0001, and then multiplying the result by 1000. In this form, the intrinsic function INT calculates the integer equivalent of 0.0001 *quanti ty, which is the value of num_bags, and subtracts it from the original, real, value. The result of this will be the amount that was lost through truncation, which in this case represents the amount of seed required in the last, partially filled, bag as a fraction of one bag. We decided in Example 4.1
142 Controlling the flow of your program
that if such a bag was. less than 10% full then the amount of seed could be ignored. It would probably be advisable, however, to add a comment to explain the test, whichever one is used!
m
ProblemWrite an external function which will return the cube root of its argument.
~ Analysis
In Section 4.3 we wrote a function to meet this requirement which was only valid for positive arguments. We can use a block IFconstruct to deal with the negative and zero argument cases, which were not included in the earlier version.
, If the argument is negative then we can use the fact that
\I( -x)
=-.yfi.The zero argument situation is, however, slightly more complicated, since it is not possible to calculate the logarithm of zero. However, rather than comparing the value of the argument with zero, which is not sensible when working with real numbers, we should rather state that if the absolute value of the argument is less than a specified small number then it is sufficiently close to zero to create possible calculation problems in the log function, and we shall therefore treat it as zero and return zero as the result of the function. Our data design and structure plan are theref~re as follows:
Data design
Purpose Type Name
A Dummy argument:
Value whose cube REAL x
root is required B Result variable:
Cube root of x REAL cube_root
C Local constant:.
A very small number REAL epsilon
Structure plan
The block IF construct 143
The only remaining question is what value to use for epsilon. Since the cube root of a positive number less than 1.0 is greater than the number itself it should not be too large, but equally it should not be so small that problems might occur with the log function. We shall, somewhat arbitrarily, use the value 10-2
°.
[II
SolutionREAL FUNCTION cube_root(x) IMPLICIT NONE
Function to calculate the cube root of a real number
Dummy argument declaration REAL, INTENT (IN) :: x
! Local constant
REAL, PARAMETER :: epsilon=lE-20
! Eliminate (nearly) zero case IF (ABS(x)<epsilon) THEN
cube_root = 0.0
Calculate cube root by using logs ELSE IF (x<O) THEN
! First deal with negative argument cube_root = -EXP(LOG(-x)/3.0) ELSE
! Positive argument cube_root = EXP(LOG(x)/3.0) END IF
END FUNCTION cube_root
One final point that should be made about this function is that calculating the logarithm and then dividing by three is not a particularly good way of calculating a cube root. We use it here to demonstrate the use of the block IF rather than introducing the lengthy mathematics that a full solution would involve!
144 Controlling the flow of your program
[!]
ProblemIn Example 4.5 we started to create two modules for use with various geometric entities. When first introducing this topic in Example 3.3 we noted that the calculation of a line joining two points required that the two points be distinct, but were not, at that time, able to check for this situation. Modify the subroutine line_two-points that was developed in Example 4.5 so that it returns an error flag to indicate that either (a) the two points were distinct, and the equation of the joining line was therefore calculated, or (b) it was not possible to calculate the line because the points were coincident.
~ Analysis
For this example, a logical error flag would seem to be most appropriate, but for some of the similar procedures which might be required for other geometric calculations there will be more than one reason for error. It is preferable that all the procedures in the module return their error information in the same way, and we shall, therefore, use an integer flag. Following a commonly-used convention, we shall return zero if the equation of the line was calculated satisfactorily, and a non-zero value if there was an error. We shall arbitrarily return -1if the two points are coincident.
The structure plan can therefore be modified as follows:
rn
SolutionWe give the complete module so that the context of the subroutine is clear.
MODULE qeometric-procedures USE qeometric_data IMPLICIT NONE CONTAINS
SUBROUTINE line_two-points(line_l,point_l,point_2,status) IMPLICIT NONE
The logical IF statement 145
! Dummy arquments
TYPE (line), INTENT (OUT) .. line 1 TYPE (point), INTENT (IN) :: point_1,point_2 INTEGER :: status
! Check to see whether points are coincident
IF (point_1%x==point_2%x .AND. point_1%y==point_2%y) THEN
! Points are coincident - return error flaq status = -1
ELSE
! Points are distinct, so calculate the coefficients
! of the eq~ation representinq the line line_1%a = point_2%y - point_1%y
line_1%b = point_1%x - point_2%x
line_1%c = point_1%y*point_2%x - point_2%y*point_1%x
! Set status to indicate success status = 0
END IF
END SUBROUTINE line_two-points END MODULE qeometric-procedures
Note that, in order to concentrate on the major issue, we have not included a tolerance factor in the test for coincident points. Since we are comparing real values for 'equality' this should be done in a final version.