0% found this document useful (0 votes)
20 views

Programming in FORTRAN

Uploaded by

nedimuzel06
Copyright
© © All Rights Reserved
Available Formats
Download as PDF or read online on Scribd
0% found this document useful (0 votes)
20 views

Programming in FORTRAN

Uploaded by

nedimuzel06
Copyright
© © All Rights Reserved
Available Formats
Download as PDF or read online on Scribd
You are on page 1/ 18
PROGRAMMING IN FORTRAN Fourth Edition Omer Akgiray PERMISSION TO COPY AND DISTRIBUTE: This book may be copied and distributed in digital or printed form provided that the front cover that contains the name of the author and the title of the book is included with each copy. Individual chapters may be copied and printed in the same way. e-mail: [email protected] 49 CHAPTER 2: DECISION STRUCTURES 2.1 Logical Constants and Variables There are only two logical constants. They are true and false, and are represented in FORTRAN as . TRUE. and . FALSE., respectively. Note that the words TRUE and FALSE are preceded and followed by periods. When logical data are printed, the letters T and F, representing . TRUE. and . FALSE., respectively, are printed without the enclosing periods. Storage locations which are used to store logical values which can be varied are called logical variables. Logical variable names must be declared by the type statement LOGICAL. For example, LOGICAL A, MOON, GOOD declares A, MOON, and GooD as logical variables. Examples of the use of logical constants and variables will be given later in this chapter. 2.2 Relational Expressions and the Single-Alternative Decision Structure FORTRAN provides a decision-making capability in the form of a construct known as the block-1F structure. The simplest form of this structure is the single-alternative decision form and has the following general format: LF (logical expression) THEN Task ENDIF A logical expression (sometimes also called a logical condition) is an expression which is either true or false (i.e., . TRUE. of . FALSE.). When the block-IF structure is executed, the logical expression is first evaluated. If the expression is true, then the program statements that constitute Task are executed. If the /ogical expression is false, then Task is skipped and execution continues with the first statement following the ENDIF statement. We shall first discuss a special class of logical expressions, namely relational expressions. A relational expression consists of two arithmetic expressions connected by a single relational operator. The following are the relational operators used in FORTRAN: = -EQ. equal to -NE. not equal to ST. greater than -LT. less than a. less than or equal to GE. greater than or equal to It should be noted that each relational operator has four characters, two letters preceded and followed by a period’. Some examples of mathematical conditions and their equivalent FORTRAN relational expressions are given below: A onto cards. As a result, all relational operators consisted of two letters enclosed between periods. 51 Before FORTRAN 77, it was necessary to use a STOP statement somewhere in the program (not necessarily at the end) to terminate execution. The END statement was not an executable statement and its only function was to signal to the compiler that there were no more lines to be compiled”. When the execution sequence of the program statements is rather straightforward, it often happens that execution is terminated at only one place, i.e. at the end of the program. In such a case, before the advent of FORTRAN 77, one would have to place a STOP statement just before the END statement. Although many FORTRAN programmers still have the habit of placing a STOP statement just before the END statement of the main program, the STOP statement is not necessary if it is immediately followed by the END statement. Note also that the END statement of a subprogram has the same effect as a RETURN statement, whereas the END statement in the main program has the same effect as a STOP statement. Example 2.1: Suppose we want to write a program that reads a number interactively, calculates the square root of the number, and displays the result on the computer screen. The intrinsic function SQRT will be used for this purpose. The square root of a negative real number, however, is not defined. It would therefore be useful to detect a negative number and print an informative error message when a negative number is entered by the user of our program. PROGRAM SQROOT HHS EEE S EIS ISSR SSE IESE SEI ESI SSSI BOE SEE I ISSIS ESTOS IO IIE © Program to calculate the square root of a real number C NUM: A real value entered by the user SHEERS ESSE HE IIIS IO ESSE SSO ECE E I I ISIS ERITH ISO IASI ISIS IIIA IMPLICIT NONE REAL NUM PRINT*, ‘Enter your number: ' READ*, NUM IF (NUM.LT.0) THEN PRINT*, ‘Square root of a negative number is not defined.' STOP ENDIF PRINT*, 'Square root is', SQRT(NUM) END Two sample runs of the program are given below: Enter your number: 4 Square root is 2.00000 Enter your number: -1 Square root of a negative number is not defined. Fortran 90 allows the use of <, <=, >, >=, and /= instead of .LT., .LE., .GT., .GE., EQ., and .NE., respectively. Note that the older forms .LT., .LE. etc. are still valid in Fortran 90. ? Organick and Meissner, p.76, Ry! The program first displays a prompting message, informing the user that a number is to be entered. Once the value of NUM is read, it is compared with zero. Note that the relational expression NUM.LT.0 compares a real variable (NUM) with an integer constant (0 without a decimal point). This is permissible because mixed-type expressions are allowed in FORTRAN 77°. The compiler handles such a mixed-type logical expression as follows. The integer constant is first converted to type real by appending a decimal point. Then comparison between two real values are carried out. If NuM is zero or positive, then the relational expression NUM.LT.O has the value .FALSE., and the program execution continues with the statement immediately following the ENDIF statement: PRINT*, 'Square root is', SQRT (NUM) The program execution is then terminated by the END statement. If, on the other hand, NuM is negative, then the value of the relational expression NUM.LT.0 will be .TRUE., and the statements within the block-IF structure are next executed. The first statement PRINT*, "Square root of a negative number is not defined.' gives information to the user about why the square root of the specified number cannot be calculated. Next, the program execution is terminated by the stor statement. Notice that, in this case, the PRINT statement that follows the ENDIF statement is never executed. This example illustrates how the sTOP statement can be used to terminate the execution of a program before reaching the END statement. Although in this text we shall always indent the statements that constitute the body (i.e., Task) of a block-IF structure, this is of course not required. It is, however, a good practice to follow as it enhances the readability of programs. This point will become more apparent as we start using more complicated contro! structures such as the multiple-alternative decision structure (Section 2.3), and especially nested block-1F statements (Section 2.6) and nested loops (Chapter 3). 2.3 The Multiple-Alternative Decision Structure The double-alternative decision structure has the following general format: IF (logical expression) THEN Task ELSE False Task ENDIF The logical expression is first evaluated. If the expression is true, then the program statements that constitute Task are executed and those that constitute False Task are skipped. If the logical expression is false, then Task is skipped and False Task is 3 Expressions of the mixed-type were not permitted in standard FORTRAN 66. Be that as it may, most of the compilers commercially available in early 1970s allowed mixed-type expressions (Organick and Meissner, p.59 and p.276). 53 executed. In either case, execution continues with the first statement following the ENDIF statement. Example 2.2: In this example we look at a subprogram that determines if a given integer is even or odd. Since there are only two possible answers (even and odd), it seems reasonable to implement this subprogram as a logical function which can return two possible values (.TRUE. or . FALSE.). Consider the following function which retums the logical value . TRUE. when the input argument N is odd: LOGICAL FUNCTION ODD (N) OHS H SEES EES E GHEE SEES I BIOS OEE HEEHSEEISISSISIOBS HERE I SISOS ISG GEE © Function returns .TRUE. if N is odd, returns .FALSE. otherwise GOES SCS HO SUISSE SSIS SEE G EIS IIIS H EE SII IG ESOT TASTE I A ATI IMPLICIT NONE INTEGER N IF(N/2*2 .EQ. N) THEN ODD = .FALSE. ELSE ODD = .TRUE. ENDIF RETURN END Alternatively, one can implement a function named EVEN that returns . TRUE. when N is even (make sure to do it). The following main program (which itself contains an example of the block-IF construct) has been written to test FUNCTION ODD: PROGRAM ODDNUM © Driver routine for FUNCTION ODD IMPLICIT NONE INTEGER NUM LOGICAL ODD PRINT*, ‘Enter the number to be tested: ' READ*, NUM IF (ODD (NUM) ) THEN PRINT*, 'The number is odd.' ELSE PRINT*, 'The number is even.' ENDIF END A sample program output follows: Enter the number to be tested: 4 The number is even. After the number (uM) has been read in, the block-1F statement is executed. The first step is the evaluation of the logical expression of the block-IF structure. The logical expression here consists of a single function reference, i.e. ODD (NUM). Control then passes into the function and the statements within the function are executed. The dummy argument N is assigned the value of the actual argument num. (Remember that there is name independence between program units.) Next, the value of N/2*2 is compared with N. Let us take a close look at this step of the function subprogram. In the arithmetic expression N/2*2 division is carried out first, ie. the expression is equivalent to (N/2)*2 34 Since both w and the constant 2 are integers, integer arithmetic is used in the evaluation of this expression. In particular, any remainder resulting from the division of two integer values is simply discarded. If is even, the remainder of the division process N/2 is zero, and N/2 is exactly equal to half of N, and therefore N/2*2 is equal to N. When N is odd, however, the remainder of the division is not zero and N/2 is less than half of n. For example, if N is 3, then N/2 is 1 (which is less than 1.5) and N/2*2 has the value 2 which is different from N. Thus, if the value of the relational expression N/2*2.EQ.N is TRUE., then N is even. Otherwise, N is odd. The name of the function is assigned the value -TRUE. if N is odd; otherwise it is set equal to .FALSE.. Control is then retumed to the calling (i.e. main) program. All of this happens during the evaluation of the logical condition of the block-1F structure in the main program. If this logical expression evaluates to .TRUE., then the string 'The number is odd. ' is displayed. Otherwise, the string 'The number is even. ' is displayed. The single-alternative and the double-alternative forms discussed earlier are special cases of the multiple-alternative decision structure (also called block-IF). The general form of the block-1F structure is as follows: IF (logical expression;) THEN Task, ELSE IF (logical expression2) THEN Task ELSE IF (logical expression,) THEN Task, ELSE False Task ENDIF The Jogical expression, is first evaluated. If it is true, then Task; is executed and all the other tasks are skipped. If logical expression, is false, then Task; is skipped and logical expression is next evaluated. Thus, logical expressiom, logical expression2, etc. are evaluated until an expression, say /ogical expression,, that evaluates to true is reached. In that case Task; is executed. If none of the conditions holds true, False Task is evaluated. In all cases, execution is continued with the first statement following ENDIF. If False Task is empty, that is, if there are no statements to be executed when all the conditions (logical expression; through logical expressionn) evaluate to false, then the word ELSE may be omitted. Example The sign function sgn(x) is commonly defined as follows: 1 if x is greater than zero or if xis zero 1 if xis strictly less than zero 55 The implementations of the FORTRAN sign transfer functions IsIGN, DSIGN, and SIGN are based on the above definition of the sign function. In particular, ISIGN(1,0) is equal to 1 and SIGN(1.,0.) is 1., because sgn(0) = 1 according to its definition. Suppose a certain application requires that we take sgn(0) to be zero. In such a case, we cannot directly use the library functions in our computations. We can, however, implement our own sign transfer function. Here is one way this can be done: PROGRAM MYSIGN © Main program to test ISIGN2 IMPLICIT NONE INTEGER NUMBER, ISIGN2 PRINT*, ‘Please enter a number: ' READ*, NUMBER PRINT*, ‘Sign =', ISIGN2(1, NUMBER) END c INTEGER FUNCTION ISIGN2(M, N) CHE BESS EEE EOS SSIS IOS BOS OE ESCH TETEEE ORCS ISOS ASSIS I IEE C User-defined sign transfer function © Works with integer arguments only GES H SEIS SSS SE IS IIE BIOS ESCO EELS CIT GOCEE III ISI IR III IMPLICIT NONE INTEGER M, N IF (N.LT.0) THEN ISIGN2 = -ABS (M) ELSE IF(N.EQ.0) THEN ISIGN2 = 0 ELSE ISIGN2 = ABS (M) ENDIF RETURN END Note that this function handles integer arguments only. You must write two other functions, say DSIGN2 and SIGN2, to handle double precision and real arguments, respectively’. Exercises: 4. The library function Mop returns the remainder of the division of its first argument by the second. Modify FUNCTION ODD of Example 2.2 to employ MoD to determine if N is odd or not. 2. Insert the following statement just before the END statement in PROGRAM MYSIGN: PRINT*, 'ISIGN(i,NUMBER) =', ISIGN(1,NUMBER) Recompile and run the program with the following values: 1, 0, and -2. Compare the values retumed by ISIGN with those calculated by ISIGN2 3. Write a program to read in three real numbers, and determine and display the largest of the three numbers (without using library functions). 4. In Example 1.13 we studied a simple function for the calculation of cube root. The following is a more generalized version of that function: REAL FUNCTION CBRT (X) HEH S HOSOI HEISE OS ECCS SSRISESEESSEE ESSE SESS G ONCE ITER IIIT I I © Calculates cube root of real number X GHC CUETO SESS OIE SITES SE HOS SSE HIEFE EI ISS IIS SO ETE IJ III: * Recall that generic library functions such as SIGN, SORT, etc. can be used with different types of arguments, It is not possible to define generic external functions in FORTRAN 77. Fortran 90 brings a solution to this problem by allowing programmers to refer to two or more functions using the same generic name (see Chapter 7), 56 IMPLICIT NONE ) THEN EXP (LOG (-X) /3.) T.0.) THEN EXP (LOG(X) /3.) Note that the algebraic equality -¥/(-x) = 2x is utilized to handle negative values of x. Write a main program to test this function. What happens if you use the expression x**(1./3) to calculate the cube root of a negative number? (Try it.) Notice also how the zero argument situation is handled. (Remember that logarithm of zero is not defined.) 2.4 Compound Logical Expressions A relational expression is a special type of logical expression. Logical expressions may also consist of logical constants (. TRUE. or . FALSE.) or logical variables used by themselves. For example, if FOUND is a logical variable, then the statement IF (FOUND) THEN PRINT*, ‘Solution found.' ELSE PRINT*, 'Cannot find solution." ENDIF will print the statement solution found. if FOUND has the value . TRUE.. If, on the other hand, FOUND has the value .FALSE. when this segment of the program is being executed, the message printed willbe Cannot find solution.. Logical expressions more complex than the simpler expressions just mentioned can be constructed using the following FORTRAN logical operators: -AND. -OR. -NOT. -EQV. .NEQV. Given any two logical expressions logexp7 and logexp2, we can form the following compound logical expressions: logexp1.AND.logexp2 logexp4.OoR.logexp2 .NoT.logexp7 logexp.EQVv.logexp2 logexp.NEQV.logexp2 m3 It should be noted that the words AND, OR, NOT, EQV, and NEQV are preceded and followed by periods. The values of these compound expressions depend on the values of logexp7 and /ogexp2 and are as follows (T and F stand for . TRUE. and . FALSE., respectively): logexp1 logexp2 logexp1.AND.logexp2 _logexp7.oR.logexp2 T T T T T E E a EF T F T E E E E - NOT. is a unary operator that changes the value of any logical expression from . TRUE. to . FALSE. or from . FALSE. to . TRUE.: logexp1 T -NOT.logexp4 J The operators .EQV. and .NEQV. are used to test logical expressions for equivalence and nonequivalence, respectively. For example, /ogexp7.£OV.logexp2 has the value . TRUE. if logexp7 and fogexp2 have the same value (both . FALSE. orboth .TRUE.). The properties of these operators are summarized below: logexp1 logexp2 logexp7.EQv.logexp2 _logexp7.NEQv.logexp2 T T T F T E FE T FE T E . FE E Tr E Note that the analogous relational operators .£Q. and .NE. are used to compare arithmetic (integer, real, double precision, complex) and character data, whereas .EQV. and .NEQV. are used to compare logical values. The latter two operators are most often used to simplify the structure of logical expressions. The following two expressions, for example, are equivalent in their effect: (A.LE.B.AND.Y.GT.X) A.LE.B .EQV. Y.GT.X +OR. (A.GT.B.AND.Y.LE.X) 58 Assume that A, B, and c are real variables, NAME1 and NAME2 are of type character and FLAG is of type logical. The following are valid logical expressions: A.GT.0.0 (A.LE.B) «AND. (C.GT.1.) FLAG -NOT. FLAG (A.LT.2.) OR. (.NOT. FLAG) NAME1.NE.NAME2 Any combination of the above logical expressions using .AND. and .oR. are also valid logical expressions. For example ((A.LT.2.).OR.(.NOT.FLAG)) .AND. (NAME1.NE.NAME2) (A.GT.0.0) .AND. (.NOT.FLAG) Similarly, any combination of the above expressions using .EQV. and .NEQV. are valid logical expressions. For example (NAMEL.NE.NAME2) .EQV. (.NOT.FLAG) (((A.LT.2) .OR. (.NOT. FLAG) ) . AND. (NAME1.NE.NAME2)) .NEQV. (A.GT.0) For clarity, extra parentheses and blanks have been used in some of the above expressions. For example, the following three expressions have the same meaning for the compiler: (A.LT.2.) .OR. (.NOT.FLAG) (A.LT.2.).OR. (.NOT. FLAG) A.LT.2..OR..NOT. FLAG In forming complicated logical expressions, the following hierarchy of operations (precedence of operators) should be remembered: 1. All subexpressions within parentheses are evaluated first. In the case of nested parenthesized subexpressions, the innermost subexpression is evaluated first. 2. A parenthesis-free subexpression is evaluated using the following hierarchy: i) Arithmetic operations a) First precedence: ” b) Second precedence: *,/ ce) Third precedence: +- ii) Relational operators (.£Q., .NE., .LT., .GT., .GE., .LE.) iii) Logical operators a) First precedence: NOT. = b) Second precedence: .AND. c) Third precedence: OR. d) Last precedence: .EQV., .NEQV. 3. Operators within the same parenthesis-free subexpression and at the same level of hierarchy (such as .£Q. and .LE.) are evaluated from left to right. We see that arithmetic operations are performed first. Relational expressions are next evaluated before compound logical expressions. Among the six relational operators, there is no priority and relational operations are carried out from left to right. When in doubt, it is better to use parentheses to explicitly specify the desired grouping of operands and operators. Furthermore, it is recommended that extra parentheses and blank spaces be used when such usage enhances the clarity and legibility of a program. For example, assuming x, Y, 2, A, B, and c are real variables, the expression (A.LT.B) .OR. ( (A-EQ.C) -AND. (B.NE. (X+Y¥/Z)) ) and its equivalent A.LT.B .OR. (A.EQ.C .AND. B.NE. (X+¥/Z)) are easier to understand than the (also equivalent and correct) statement A.LT.B.OR.A.EQ.C.AND.B.NE.X+¥/Z Example 2.4: In this example we will look at a function subprogram that can be used to determine if a given year is a leap year or not. Remember that a leap year is a year containing 366 days with February 29 as the extra day. The function is named LEAPYR, and is a logical function that returns . TRUE. if the given year is a leap year; returns . FALSE. otherwise. Agiven year is a leap year if it is evenly divisible by 4 and not by 100, or it is evenly divisible by 400. For example, 1999 is not a leap year (1999 is not evenly divisible by 4), whereas 2000 is a leap year (evenly divisible by 400). Similarly, year 1900 is not a leap year (it is evenly divisible by 100 but not by 400). Notice how a compound logical expression is used in the program to implement the definition of a leap year. LOGICAL FUNCTION LEAPYR (YEAR) OHHH SEES EEE EHS S SI ISG SGI TIC ONOS SSSI TESS ISIS TCG I C Returns .TRUE. if YEAR is a leap year; returns .FALSE. otherwise GHEE USES IEC G HUES UB SHOOTS SSIES SIS I OSIS DIES IES OEIGE ITE IASI IMPLICIT NONE INTEGER YEAR, REM4, REM100, REM400 REM4 = MOD(YEAR, 4) REMLOO = MOD(YEAR, 100) REM400 = MOD(YEAR, 400) TE( (REM4.EQ.0 .AND. REMIO0.NE.0) .OR. REM400.5Q.0) THEN LEAPYR = .TRUE. ELSE LEAPYR = . FALSE. ENDIF RETURN END 60 The function employs the variables named REM4, REM100, and REM400 to store the computed remainders of the year after division by 4, 100, and 400 respectively. The extra parentheses around the expression REM4.EQ.0 .AND. REM1OO.NE.0 are actually not necessary since .anp. has higher precedence than .or.. Similarly, extra blanks are used within the compound logical expression to improve readability. The following version, therefore, would also be perfectly valid (although less legible): TF (REM4.EQ.0.AND.REM100.NE.0.OR.REM400.EQ.0) THEN Note that the following version would also give correct results in this case (why?): IE (REM4.EQ.0 .AND. (REM1O0.NE.0 .OR. REM400.8Q.0) )THEN The following is a main program that can be used to test FUNCTION LEAPYR: PROGRAM LEAP IMPLICIT NONE INTEGER YEAR LOGICAL LEAPYR PRINT*, ‘Enter the year to be tested: ' READ*, YEAR IF (LEAPYR (YEAR) ) THEN PRINT*, 'It is a leap year.' ELSE PRINT*, 'No, it is not a leap year.' ENDIF END Example 2. This example provides another illustration of the use of compound logical expressions constructed using the operators .AND. and .OR. PROGRAM LETGRD ¢ Program determines and prints the letter grade € corresponding to a score between 0 and 100. IMPLICIT NONE INTEGER SCORE CHARACTER*1 GRADE, LETTER PRINT*, 'Enter the score (between 0 and 100): READ*, SCORE LETTER = GRADE (SCORE) IF(LETTER.EQ.'*') THEN PRINT 1, 'Score entered’, SCORE, 'is out of range:' PRINT 1, 'You must enter a value between 0 and 100.’ ELSE PRINT 1, ‘Score =', SCORE, '=> Letter Grade =', LETTER ENDIF 1 FORMAT(1X, A, :, 1X, 14, 1X, A, :, 1X, A) END CHARACTER*1 FUNCTION GRADE (SCORE) OSE C CEES ESE SESE O SSO ICI T EIS UISSISSIORC TEE EEEEEESIISISS II ISSO IO © Returns letter grades ‘A', 'B', 'C', 'D', or 'F' for specified C SCORE between 0 and 100. Returns '*' if SCORE is out of range. HSCEI IE SIE ESSE IIT SO SSRN ICUS E EIEIO RIS HEE IS GOO I TAI II I IMPLICIT NONE INTEGER SCORE IF(SCORE.LT.O .OR. SCORE.GT.100) THEN GRADE = '*! ELSE IF(SCORE.GE.90 .AND, SCORE.LE.100) THEN GRADE = 'A' ELSE IF(SCORE.GE.80 .AND. SCORE.LE. 89) THEN GRADE = 'B! ELSE IF (SCORE.GE.70 .AND. SCORE.LE. 79) THEN GRADE = 'C! ELSE IF(SCORE.GE.60 .AND. SCORE.LE. GRADE = 'D! ELSE IF(SCORE.GE. 0 .AND. SCORE.LE. GRADE = 'F! ENDIF RETURN END 61 69) THEN 59) THEN It may be noted that, the block-1F structure within the function could be replaced by IF(SCORE.LT.O GRADE = '*! ELSE IF (SCORE.GE.90) THEN GRADE = 'At ELSE IF (SCORE.GE.80) THEN GRADE = 'B! ELSE IF (SCORE.GE.70) THEN GRADE = 'C ELSE IF (SCORE.GE.60) THEN GRADE = 'D! ELSE GRADE = 'F! ENDIF -OR. SCORE.GT.100) THEN This version is simpler (requires less typing). It was decided, however, that explicitly specifying the range of scores that gets each letter grade made the logic of the program more easily understandable. Example 2. Logical expressions are most frequently used to specify conditions in decision statements, and (as we shall see in the next chapter) in DO WHILE loops. They may also be used in assignment statements involving logical variables. This is usually done to simplify the listing of conditions in a block-1F structure. An example is provided below. PROGRAM ORDER Cc Reads in three numbers and determines their relative sizes C >= means greater than or equal to © > means strictly greater than IMPLICIT NONE REAL X, Y, Z LOGICAL Ll, L2, 13 PRINT*, ‘Enter X, Y, 2: ' READ*, X, ¥, Z Ll = X.GE.Y L2 = X.GE.Z L3 = ¥.GE.Z TE(L1 .AND. L3)THEN PRINT*, 'X >= Y >= 2! ELSE IF(L1 .AND. 12) THEN c L1.AND.L2 is .TRUE., but L1.AND.13 is . FALSE. c This can happen only if L3 is .FALSE, whereas L1 and L2 are .TRUE. PRINI*, 'X >= Z > ¥! BLSE IF (L1) THEN c Ll is .TRUE., but both L1.AND.L2 and L1.AND.L3 are .FALSE. c This can happen only if both L2 and L3 are .FALSE PRINT*, 'Z > X >= Y! ELSE IF (L2) THEN c L2 is .TRUE., but Ll is .FALSE. PRINT, '¥ > X >= 2! ELSE IF(L3) THEN G 13 is .TRUE., Ll and L2 are .FALSE. PRINT*, '¥ >= Z > x! ELSE c Ll, L2 and L3 are all . FALSE. PRINT*, 'Z > Y > X' ENDIF END

You might also like