Outline of Fortran 90 Topics • • • • • • • • • • • • • Overview of Computing (Prog. in Fortran 90/95, chapt 1-4) – Computer Organization – Languages – Problem Solving Data, Part 1 (chapt 5-6, 10) – Fortran 90 Character Set – Variables – Numeric Data Types Numeric Expressions Data, Part 2: Logicals and Logical Expressions (chapt 6, 11) Control Structures, Part 1: Branching (IF-THEN) (chapt 12) Data, Part 1B: More Detail About Integers Control Structures, Part 2: Repetition (DO) (chapt 13) Data, Part 3: 1D Arrays (chapt 7) Procedures – Functions ∗ Intrinsic (chapt 17) ∗ User-Defined (chapt 14) – Subroutines (chapt 15) Data, Part 4: Multidimensional Arrays Data, Part 5: Characters Data, Part 6: Derived Data Types Input/Output – Formatting – Files Note: this is roughly how this part of the semester will go. 1 Functions in Mathematics “In mathematics a function is a rule that assigns to each element of a set an element of the same or of another set.” (Bers & Karal, Calculus, 2nd ed, Holt, Reinhart & Winston, 1976, p. 50.) So, for example, if we have a function f (x) = x + 1 then we know that f (−2.5) f (−2) f (−1) f (0) f (+1) f (+2) f (+2.5) = = = = = = = ... −2.5 −2 −1 0 +1 +2 +2.5 ... + + + + + + + 1 1 1 1 1 1 1 = = = = = = = −1.5 −1 0 +1 +2 +3 +3.5 = = = = = = = 2.5 2 1 0 1 2 2.5 Likewise, if we have a function a(y) = |y| then we know that a(−2.5) a(−2) a(−1) a(0) a(+1) a(+2) a(+2.5) = = = = = = = ... | −2.5 | −2 | −1 | 0 | +1 | +2 | +2.5 ... | | | | | | | We refer to the thing inside the parentheses – in the first example it’d be x, and in the second example y – as the argument (or sometimes the parameter) of the function. 2 Functions in Fortran 90 From time to time in our programs we’ve seen things like this: IF (money_owed_or_received < & & amount_cannot_be_negative) THEN money_owed = ABS(money_owed_or_received) PRINT *,"You owe the goverment $", & & NINT(money_owed),"." ELSE money_received = money_owed_or_received PRINT *,"The government owes you $", & & NINT(money_received),"." END IF So, what do ABS and NINT do? ABS is a function that calculates the absolute value of its argument. It is the Fortran 90 analogue of the mathematical function a that we just looked at. So: ... ABS(-2.5) ABS(-2) ABS(-1) ABS(0) ABS(1) ABS(2) ABS(2.5) ... → → → → → → → 2.5 2 1 0 1 2 2.5 (Note: here → denotes “evaluates to.”) Similarly, NINT calculates the nearest integer to the given real value, by rounding. An important distinction between a mathematical function and a Fortran 90 function: a mathematical function is simply a definition, while a Fortran 90 function does stuff. More on this shortly. 3 A Quick Look at ABS % cat abstest.f90 PROGRAM abstest IMPLICIT NONE PRINT *, "ABS(-2.5) = ", PRINT *, "ABS(-2) = ", PRINT *, "ABS(-1) = ", PRINT *, "ABS( 0) = ", PRINT *, "ABS( 1) = ", PRINT *, "ABS( 2) = ", PRINT *, "ABS( 2.5) = ", END PROGRAM abstest % f90 -o abstest abstest.f90 % abstest ABS(-2.5) = 2.500000 ABS(-2) = 2 ABS(-1) = 1 ABS( 0) = 0 ABS( 1) = 1 ABS( 2) = 2 ABS( 2.5) = 2.500000 ABS(-2.5) ABS(-2) ABS(-1) ABS( 0) ABS( 1) ABS( 2) ABS( 2.5) (An interesting property of ABS: if its argument is an INTEGER, it returns an INTEGER result, but if its argument is a REAL, it returns a REAL result. More on this later.) Jargon: in programming, the use of a function in an expression is referred to as an invocation, or more colloquially as a call. We say that the statement PRINT *, ABS(-2) invokes or calls the function ABS; the statement passes an argument of -2 to the function; the function ABS returns a value of 2. 4 Domain & Range In mathematics, the set of numbers that can be arguments into a given function is referred to as the domain of that function. Similarly, the set of numbers that can be results of a given function is referred to as the range of that function. For example, in the case of the function f (x) = x + 1 we define the domain to be the set of real numbers (sometimes denoted <), which means that the x in f (x) can be any real number. Likewise, we also define the range to be the set of real numbers, because for every real number y there is some real number x such that f (x) = y. On the other hand, for a function 1 x−1 the domain cannot include 1, because q(x) = 1 1 = 1−1 0 which is undefined. So the domain might be < − {1}. q(1) = In that case, the range of q would be the set of all real numbers except 0, because there’s no real number y such that 1/y is 0. (Note: if you’ve taken calculus, you’ve seen that, as y gets arbitrarily large, 1/y approaches 0 as a limit – but “gets arbitrarily large” is not a real number, and neither is “approaches 0 as a limit.”) Fortran 90 has concepts that are analogous to the mathematical domain and range: argument type and return type. More on these shortly. 5 Intrinsic Functions in Fortran 90 Fortran 90 has a bunch of functions built into the language. These functions are referred to as intrinsic functions, where intrinsic means “built into the language.” A few examples: Function name ABS(x) SQRT(x) EXP(x) LOG(x) LOG10(x) SIN(x) COS(x) TAN(x) CEILING(x) FLOOR(x) HUGE(x) TINY(x) Math Name Value Example absolute value |x| ABS(-1.0) 1/2 square root x SQRT(2.0) x exponential e EXP(1.0) natural logarithm ln x EXP(2.718...) common logarithm log x LOG10(100.0) sine sin x SIN(3.14...) cosine cos x COS(3.14...) tangent tan x TAN(3.14...) least integer ≥ x dxe CEILING(2.5) greatest integer ≤ x bxc FLOOR(2.5) largest possible value of same type as x smallest positive value of same type as x → → → → → → → → → → 1.0 1.414... 2.718... 1.0 2.0 0.0 -1.0 0.0 3 2 You’ll find an exhaustive list of all of Fortran 90’s intrinsic functions (more than 100 of them) in Programming in Fortran 90/95, Chapter 17, pages 205-208. 6 Function Arguments In mathematics, the argument of a function can be: • a number: • • • • f (5) = 5 + 1 = 6 a variable: f (z) = z + 1 an arithmetic expression: f (5 + 7) = (5 + 7) + 1 = 12 + 1 = 13 another function: f (a(w)) = |w| + 1 any combination of these; i.e., any general expression whose value is in the domain of the function: f (3a(5w + 7)) = 3 (|5w + 7|) + 1 Likewise, in Fortran 90 the argument of a function can be any nonempty expression, including an expression containing a function, as long as the expression that constitutes the argument evaluates to the appropriate type. 7 Function Argument Example % cat funcargs.f90 PROGRAM funcargs IMPLICIT NONE REAL,PARAMETER :: pi = 3.1415926 REAL :: angle_in_radians PRINT ’(A,F9.7,A,F10.7)’, & & "COS(", 1.5707963, ") = ", COS(1.5707963) PRINT ’(A,F9.7,A,F10.7)’, "COS(", pi, ") = ", & & COS(pi) PRINT ’(A)’, "Enter an angle in radians:" READ *, angle_in_radians PRINT ’(A,F10.7,A,F10.7)’, & & "COS(", angle_in_radians, ") = ", & & COS(angle_in_radians) PRINT ’(A,F10.7,A,F10.7)’, & & "ABS(COS(", angle_in_radians, ")) = ", & & ABS(COS(angle_in_radians)) PRINT ’(A,F10.7,A,F10.7)’, & & "COS(ABS(", angle_in_radians, ")) = ", & & COS(ABS(angle_in_radians)) PRINT ’(A,F10.7,A,F10.7)’, & & "ABS(COS(2.0 * ", angle_in_radians, ")) = ", & & ABS(COS(2.0 * angle_in_radians)) PRINT ’(A,F10.7,A,F10.7)’, & & "ABS(2.0 * COS(", angle_in_radians, ")) = ", & & ABS(2.0 * COS(angle_in_radians)) PRINT ’(A,F10.7,A,F10.7)’, & & "ABS(2.0 * COS(1.0 / 5.0 * ", & & angle_in_radians, ")) = ", & & ABS(2.0 * COS(1.0 / 5.0 * angle_in_radians)) END PROGRAM funcargs % f90 -o funcargs funcargs.f90 % funcargs COS(1.5707963) = 0.0000001 COS(3.1415925) = -1.0000000 Enter an angle in radians: -3.1415926 COS(-3.1415925) = -1.0000000 ABS(COS(-3.1415925)) = 1.0000000 COS(ABS(-3.1415925)) = -1.0000000 ABS(COS(2.0 * -3.1415925)) = 1.0000000 ABS(2.0 * COS(-3.1415925)) = 2.0000000 ABS(2.0 * COS(1.0 / 5.0 * -3.1415925)) = 1.6180340 8 Using Functions Functions are used in expressions in exactly the same ways that variables are used. For example, a function call can be used on the right hand side of an assignment: REAL :: theta, cos_theta theta = 3.1415926 / 4.0 cos_theta = COS(theta) A function call can also be used in an expression in list-directed output: PRINT *, 2.0 PRINT *, COS(theta) ** 2.0 And, since any expression can be used as some function’s argument, a function call can also be used as an argument to another function: REAL,PARAMETER :: pi = 3.1415926 PRINT *, 1 + COS(ASIN(SQRT(2.0)/2.0) + pi) However, most function calls cannot be used in initialization: ! ILLEGAL ILLEGAL ILLEGAL REAL :: theta, cos_theta = COS(3.1415926) ! ILLEGAL ILLEGAL ILLEGAL Nor can most function calls be used in named constant declaration: ! ILLEGAL ILLEGAL ILLEGAL REAL,PARAMETER :: cos_theta = COS(3.1415926) ! ILLEGAL ILLEGAL ILLEGAL As a general rule, it’s best not to try to use function calls in initializations or in named constant declarations. 9 Function Use Example % cat funcuse.f90 PROGRAM funcuse IMPLICIT NONE REAL,PARAMETER :: pi = 3.1415926 ! REAL,PARAMETER :: sin_pi = SIN(3.1415926) ! ILLEGAL ! REAL :: sin_pi = SIN(pi) ! ILLEGAL ! REAL :: sin_pi = SIN(3.1415926) ! ILLEGAL REAL :: theta, sin_pi, sin_theta theta = 3.0 * pi / 4 sin_pi = SIN(pi) sin_theta = SIN(theta) PRINT *, "2.0 = ", 2.0 PRINT *, "pi = ", pi PRINT *, "theta = ", theta PRINT *, "SIN(pi) = ", SIN(pi) PRINT *, "sin_pi = ", sin_pi PRINT *, "SIN(theta) = ", SIN(theta) PRINT *, "sin_theta = ", sin_theta PRINT *, "SIN(theta) ** (1.0/3.0) = ", & & SIN(theta) ** (1.0/3.0) PRINT *, "1 + SIN(ACOS(1.0)) = ", & & 1 + SIN(ACOS(1.0)) PRINT *, "SIN(ACOS(1.0)) = ", & & SIN(ACOS(1.0)) PRINT *, "SQRT(2.0) = ", & & SQRT(2.0) PRINT *, "SQRT(2.0) / 2 = ", & & SQRT(2.0) / 2 PRINT *, "ACOS(SQRT(2.0)/2.0) = ", & & ACOS(SQRT(2.0)/2.0) PRINT *, "SIN(ACOS(SQRT(2.0)/2.0)) = ", & & SIN(ACOS(SQRT(2.0)/2.0)) END PROGRAM funcuse % f90 -o funcuse funcuse.f90 % funcuse 2.0 = 2.000000 pi = 3.141593 theta = 2.356194 SIN(pi) = 1.5099580E-07 sin_pi = 1.5099580E-07 SIN(theta) = 0.7071068 sin_theta = 0.7071068 SIN(theta) ** (1.0/3.0) = 0.8908987 1 + SIN(ACOS(1.0)) = 1.000000 SIN(ACOS(1.0)) = 0.0000000E+00 SQRT(2.0) = 1.414214 SQRT(2.0) / 2 = 0.7071068 ACOS(SQRT(2.0)/2.0) = 0.7853982 SIN(ACOS(SQRT(2.0)/2.0)) = 0.7071068 10 Evaluation of Functions in Expressions When a function call appears in an expression – for example, on the right hand side of an assignment statement – the function is evaluated just before its value is needed according to precedence order. For example, if x and y are REAL variables, and y has already been assigned the value -10.0, then the assignment statement x = 1 + 2.0 * 8.0 + ABS(y) / 4.0 is evaluated like so: x = 1 + 2.0 * 8.0 + ABS(y) / 4.0 ⇒ ABS(y) / 4.0 ⇒ x = 1 + 16.0 + x = 1 + 16.0 + ABS(-10.0) / 4.0 ⇒ x = 1 + 16.0 + x = 1 + 16.0 + 2.5 ⇒ x = 1.0 + 16.0 + 2.5 ⇒ + 2.5 ⇒ x = x = 17.0 10.0 / 4.0 ⇒ 19.5 So, the variable x is ultimately assigned the value 19.5. 11 Functions with Multiple Arguments In mathematics, we sometimes have functions that have multiple arguments: h(x, y) = xy + 2x + 3y + 5 In this case, we know that h(−2.5, −1.5) h(−2, −0.5) h(−1, 1.25) h(0, 0) = = = = (−2.5)(−1.5) (−2)(−0.5) (−1)(1.25) (0)(0) + + + + (2)(−2.5) (2)(−2) (2)(−1) (2)(0) + (3)(−1.5) + 5 = −0.75 + (3)(−0.5) + 5 = +0.5 + (3)(1.25) + 5 = 0 + (3)(0) + 5 = +5 Here, we define the domain of the first argument of h to be the set of all real numbers <, and likewise we define the domain of the second argument of h to be the set of all real numbers <, so the domain of h as a whole is < × <. Since the result of h is a single real value, we define the range of h to be <, and we denote the mapping as h : < × < 7→ < Similarly, in Fortran 90 we have intrinsic functions with multiple arguments. Examples include: Function name MIN(x,y) MAX(x,y) MOD(x,y) BTEST(x,y) Math Name minimum maximum remainder bit test Math Value if (x < y) then x else y if (x > y) then x else y x - INT(x / y) · y .TRUE. if the yth bit of x is 1 Functions with multiple arguments can be used in exactly the same ways as functions with a single argument. The order of the arguments in the function call is extremely important. 12 Intrinsic Functions Are Not Enough Often, we have a particular kind of value that we need to calculate over and over again, under a variety of circumstances. For example, in our income tax program (Programming Project #3), we had to calculate the total tax, based on the taxable income as well as on the base tax, lower bound of income and tax rate for the appropriate tax bracket: & & & & & & & & & & & & & & & & IF (filing_status == single_status) THEN IF (taxable_income > no_income) .AND. & (taxable_income <= lower_bound_single1) THEN total_tax = & base_tax_single1 + & tax_rate_single1 * & (taxable_income - lower_bound_single1) ELSE IF (taxable_income > lower_bound_single1) .AND. & (taxable_income <= lower_bound_single2) THEN total_tax = & base_tax_single2 + & tax_rate_single2 * & (taxable_income - lower_bound_single2) ... END IF ELSE IF (taxable_income > no_income) .AND. & (taxable_income <= lower_bound_married1) THEN total_tax = & base_tax_married1 + & tax_rate_married1 * & (taxable_income - lower_bound_married1) ELSE IF (taxable_income > lower_bound_married1) .AND. & (taxable_income <= lower_bound_married2) THEN total_tax = & base_tax_married2 + & tax_rate_married2 * & (taxable_income - lower_bound_married2) ... END IF END IF Notice that the assignment & & & total_tax = & base_tax_x + & tax_rate_x * & (taxable_income - lower_bound_x) keeps coming up over and over again. 13 User-Defined Functions So, it’d be nice to replace the assignment & & & total_tax = & base_tax_x + & tax_rate_x * & (taxable_income - lower_bound_x) with a function call that would calculate the total tax for us: & & & total_tax = & total_tax_for_bracket( & taxable_income, base_tax_married2, & tax_rate_married2, lower_bound_married2) Obviously, the designers of Fortran 90 weren’t able to anticipate that we might want a function to do this – as well as a zillion other things that we might need functions to do – so there’s no intrinsic function to calculate something completely application-specific such as total tax. Instead, we as users of Fortran 90 are going to have to define our own function to do it: REAL FUNCTION total_tax_for_bracket ( & & taxable_income, base_tax, tax_rate, & & lower_bound) IMPLICIT NONE REAL :: taxable_income, base_tax, & & tax_rate, lower_bound total_tax_for_bracket = & & base_tax + & & tax_rate * & & (taxable_income - lower_bound) END FUNCTION total_tax_for_bracket Because we as users of Fortran 90 define this kind of function our own personal selves, this kind of function is referred to as a userdefined function. 14 General Form of User-Defined Function Definitions In general, the definition of a user-defined function looks an awful lot like a program, except that: 1. The keyword PROGRAM is replaced by the keyword FUNCTION. So, the function has a FUNCTION statement and an END FUNCTION statement, which are analogous to a program’s PROGRAM statement and END PROGRAM statement. 2. In the FUNCTION statement, immediately before the keyword FUNCTION is a data type that declares the return type, which is the type of the value that the function will return and (for now) must be a basic scalar type (e.g., INTEGER, REAL). 3. In the FUNCTION statement, immediately after the function name is a list of arguments, enclosed in parentheses and separated by commas (just as in a mathematical function). 4. In the function’s declaration section, the arguments must be declared exactly as if they were variables. Good programming style is to declare local named constants, then arguments, then local variables (though in principle this order is not required). 5. In the body of the function, the function name is used as if it were a variable, and it must be assigned the value that the function is to return, so it’s referred to as the return variable. returntype FUNCTION funcname ( arg1, arg2, ... ) IMPLICIT NONE localconst1type,PARAMETER :: localconst1 localconst2type,PARAMETER :: localconst2 ... arg1type, arg1attributes :: arg1 arg2type, arg2attributes :: arg2 ... localvar1type, localvar1attributes :: localvar1 localvar2type, localvar2attributes :: localvar2 ... [ function body: does stuff ] funcname = returnvalue END FUNCTION funcname 15 User-Defined Function Example Here’s a definition of a user-defined function. REAL FUNCTION cube_root (original_value) IMPLICIT NONE REAL :: original_value REAL,PARAMETER :: cube_root_power = 1.0 / 3.0 cube_root = original_value ** cube_root_power END FUNCTION cube_root What can we say about this user-defined function? 1. 2. 3. 4. Its name is cube root. Its return type is REAL. It has one argument, whose type is REAL. It calculates and returns the cube root of the incoming argument. So, cube root calculates the cube root of a REAL argument and returns a REAL result whose value is the cube root of the argument. Does the name of a user-defined function have to be meaningful? Absolutely not; you could easily have a function called square root that always returns 12. But that’d be REALLY REALLY DUMB, and you’d get a VERY BAD GRADE. Jargon: for a particular user-defined function, the set of statements from the FUNCTION statement through the END FUNCTION statement is referred to as that particular function unit. Likewise, the set of statements from the PROGRAM statement through the END PROGRAM statement is referred to as the program unit. 16 Using a User-Defined Function: Example #1 % cat cube_root_scalar.f90 PROGRAM cube_root_scalar IMPLICIT NONE REAL :: input_value1, cube_root_value1 REAL :: input_value2, cube_root_value2 REAL :: input_value3, cube_root_value3 REAL,EXTERNAL :: cube_root ! Function return type PRINT *, "What 3 real numbers would you like ", & & "the cube roots of?" READ *, input_value1, input_value2, input_value3 cube_root_value1 = cube_root(input_value1) cube_root_value2 = cube_root(input_value2) cube_root_value3 = cube_root(input_value3) PRINT *, "The cube root of ", input_value1, & & " is ", cube_root_value1, "." PRINT *, "The cube root of ", input_value2, & & " is ", cube_root_value2, "." PRINT *, "The cube root of ", input_value3, & & " is ", cube_root_value3, "." END PROGRAM cube_root_scalar REAL FUNCTION cube_root (original_value) IMPLICIT NONE REAL :: original_value REAL,PARAMETER :: cube_root_power = 1.0 / 3.0 cube_root = original_value ** cube_root_power END FUNCTION cube_root % f90 -o cube_root_scalar cube_root_scalar.f90 % cube_root_scalar What 3 real numbers would you like the cube roots of? 1, 8, 100 The cube root of 1.000000 is 1.000000 . The cube root of 8.000000 is 2.000000 . The cube root of 100.0000 is 4.641589 . Notice this declaration: REAL,EXTERNAL :: cube root This declaration tells the compiler that there’s a function called cube root with a return type of REAL, and that it’s declared external to (outside of) the program unit. 17 User-Defined Function Example #2: Array % cat cube_root_array.f90 PROGRAM cube_root_array IMPLICIT NONE INTEGER,PARAMETER :: numvalues = 5 REAL,DIMENSION(numvalues) :: & & input_value, cube_root_value INTEGER :: i REAL,EXTERNAL :: cube_root ! Function return type PRINT *, "What ", numvalues, " real numbers" PRINT *, " would you like the cube roots of?" READ *, (input_value(i), i = 1, numvalues) DO i = 1, numvalues cube_root_value(i) = cube_root(input_value(i)) END DO DO i = 1, numvalues PRINT *, "The cube root of ", & & input_value(i), " is ", cube_root_value(i), "." END DO END PROGRAM cube_root_array REAL FUNCTION cube_root (original_value) IMPLICIT NONE REAL :: original_value REAL,PARAMETER :: cube_root_power = 1.0 / 3.0 cube_root = original_value ** cube_root_power END FUNCTION cube_root % f90 -o cube_root_array cube_root_array.f90 % cube_root_array What 5 real numbers would you like the cube roots of? 1, 8, 100, 125, 400 The cube root of 1.000000 is 1.000000 The cube root of 8.000000 is 2.000000 The cube root of 100.0000 is 4.641589 The cube root of 125.0000 is 5.000000 The cube root of 400.0000 is 7.368063 18 . . . . . Reusing User-Defined Functions via the INCLUDE Statement In the previous two example programs, the function definitions for cube root were character-for-character identical. It’d be a big waste of time to have to type the exact same function over and over if we wanted to use it multiple times. So, Fortran 90 allows us to put the function in a completely separate .f90 source file, and to include it when we’re compiling, by using an INCLUDE statement. The general form of the INCLUDE statement is: INCLUDE ’filename’ or: INCLUDE "filename" For example: INCLUDE ’cube root.f90’ What Does This Mean? When the compiler encounters an INCLUDE statement, it brings the contents of the included source file into the primary source file that it’s currently compiling. So, the upshot is that the compiler ends up treating the primary source file exactly as if you had typed the contents of the included file into it, in the place where the INCLUDE statement is. This promotes code reuse. 19 INCLUDE Statement Example #1 % cat cube_root_scalarinc.f90 PROGRAM cube_root_scalarinc IMPLICIT NONE REAL :: input_value1, cube_root_value1 REAL :: input_value2, cube_root_value2 REAL :: input_value3, cube_root_value3 REAL,EXTERNAL :: cube_root ! Function return type PRINT *, "What 3 real numbers would you like ", & & "the cube roots of?" READ *, input_value1, input_value2, input_value3 cube_root_value1 = cube_root(input_value1) cube_root_value2 = cube_root(input_value2) cube_root_value3 = cube_root(input_value3) PRINT *, "The cube root of ", input_value1, & & " is ", cube_root_value1, "." PRINT *, "The cube root of ", input_value2, & & " is ", cube_root_value2, "." PRINT *, "The cube root of ", input_value3, & & " is ", cube_root_value3, "." END PROGRAM cube_root_scalarinc INCLUDE ’cube_root.f90’ % cat cube_root.f90 REAL FUNCTION cube_root (original_value) IMPLICIT NONE REAL :: original_value REAL,PARAMETER :: cube_root_power = 1.0 / 3.0 cube_root = original_value ** cube_root_power END FUNCTION cube_root % f90 -o cube_root_scalarinc cube_root_scalarinc.f90 % cube_root_scalarinc What 3 real numbers would you like the cube roots of? 1, 8, 100 The cube root of 1.000000 is 1.000000 . The cube root of 8.000000 is 2.000000 . The cube root of 100.0000 is 4.641589 . 20 INCLUDE Statement Example #2 % cat cube_root_arrayinc.f90 PROGRAM cube_root_arrayinc IMPLICIT NONE INTEGER,PARAMETER :: numvalues = 5 REAL,DIMENSION(numvalues) :: & & input_value, cube_root_value INTEGER :: i REAL,EXTERNAL :: cube_root ! Function return type PRINT *, "What ", numvalues, " real numbers" PRINT *, " would you like the cube roots of?" READ *, (input_value(i), i = 1, numvalues) DO i = 1, numvalues cube_root_value(i) = cube_root(input_value(i)) END DO DO i = 1, numvalues PRINT *, "The cube root of ", & & input_value(i), " is ", cube_root_value(i), "." END DO END PROGRAM cube_root_arrayinc INCLUDE "cube_root.f90" % cat cube_root.f90 REAL FUNCTION cube_root (original_value) IMPLICIT NONE REAL :: original_value REAL,PARAMETER :: cube_root_power = 1.0 / 3.0 cube_root = original_value ** cube_root_power END FUNCTION cube_root % f90 -o cube_root_arrayinc cube_root_arrayinc.f90 % cube_root_arrayinc What 5 real numbers would you like the cube roots of? 1, 8, 100, 125, 400 The cube root of 1.000000 is 1.000000 The cube root of 8.000000 is 2.000000 The cube root of 100.0000 is 4.641589 The cube root of 125.0000 is 5.000000 The cube root of 400.0000 is 7.368063 21 . . . . . Actual Arguments vs. Formal Arguments In our cube root examples, we’ve seen function calls that look like this: cube root value1 = cube root(input value1) We say that • this assignment statement • calls the user-defined function cube root • using as its actual argument the variable input value1 • which corresponds to the function definition’s formal argument original value • and returns the cube root of • the value of stored in the variable input value1. The actual argument is the argument that appears in the call to the function (for example, in the main program). The formal argument – which in Fortran 90 is also called the dummy argument – is the argument that appears in the definition of the function. Not surprisingly, the mathematical case is the same. In a mathematical function definition like f (x) = x + 1 if we want the value of f (1) then x is the formal argument of the function f , and 1 is the actual argument. 22 Side Effects A side effect of a function is something that the function does other than calculate and return its return value, and that affects something other than the values of local variables. For example, consider this function: INTEGER FUNCTION input_number_of_elements ( & & minimum_number_of_elements, & & maximum_number_of_elements) IMPLICIT NONE INTEGER :: minimum_number_of_elements, & & maximum_number_of_elements INTEGER :: numelts DO PRINT *, "How many elements would you like" PRINT *, " the array to have" PRINT *, " (between ", & & minimum_number_of_elements, " and ", & & maximum_number_of_elements, ")?" READ *, numelts IF (numelts < & & minimum_number_of_elements) THEN PRINT *, "Too few, idiot!" ELSE IF (numelts > & & maximum_number_of_elements) THEN PRINT *, "Too many, fool!" ELSE EXIT END IF END DO input_number_of_elements = numelts END FUNCTION input_number_of_elements This function has the side effect of outputting a prompt message to the user, as well as outputting an error message if needed, and doing so multiple times, until the user inputs an appropriate value. Notice that the READ statement inputs into a local variable called numelts, and at the end of the function the value of numelts is assigned to the return variable input number of elements, which makes the program easier to read. 23 Side Effects Example % cat userarray.f90 PROGRAM userarray IMPLICIT NONE INTEGER,PARAMETER :: first_element = 1 INTEGER,PARAMETER :: minnumelements = 1 INTEGER,PARAMETER :: maxnumelements = 100 REAL,DIMENSION(first_element:maxnumelements) & & :: element INTEGER :: number_of_elements INTEGER :: input_number_of_elements number_of_elements = & & input_number_of_elements( & & minnumelements, maxnumelements) PRINT ’(A,I3,A)’, & & "The number of elements you plan to input is ", & & number_of_elements, "." END PROGRAM userarray INCLUDE ’inputnumelts.f90’ % cat inputnumelts.f90 INTEGER FUNCTION input_number_of_elements ( & & minimum_number_of_elements, & & maximum_number_of_elements) IMPLICIT NONE INTEGER :: minimum_number_of_elements, & & maximum_number_of_elements INTEGER :: numelts DO PRINT *, "How many elements would you like" PRINT *, " the array to have" PRINT *, " (between ", & & minimum_number_of_elements, " and ", & & maximum_number_of_elements, ")?" READ *, numelts IF (numelts < & & minimum_number_of_elements) THEN PRINT *, "Too few, idiot!" ELSE IF (numelts > & & maximum_number_of_elements) THEN PRINT *, "Too many, fool!" ELSE EXIT END IF END DO input_number_of_elements = numelts END FUNCTION input_number_of_elements % f90 -o userarray userarray.f90 % userarray How many elements would you like the array to have (between 1 and 100 )? 0 Too few, idiot! How many elements would you like the array to have (between 1 and 100 )? 101 Too many, fool! How many elements would you like the array to have (between 1 and 100 )? 50 The number of elements you plan to input is 50. 24 A Function That Doesn’t Return a Value So, suppose we wanted, for example, to read in a given number of values into an array. We might define a function like so: INTEGER FUNCTION input_elements ( & element, number_of_elements, & minimum_number_of_elements, & maximum_number_of_elements) IMPLICIT NONE INTEGER :: number_of_elements INTEGER :: minimum_number_of_elements, & & maximum_number_of_elements REAL,DIMENSION(minimum_number_of_elements: & maximum_number_of_elements) :: & element INTEGER :: numelts, e PRINT *, "What are the ", number_of_elements, & " elements of the array?" READ *, (element(e), & & e = minimum_number_of_elements, & & maximum_number_of_elements) input_elements = 000 END FUNCTION input_elements & & & & & & What on earth are we going to return? The best answer is, we’re not going to return anything. But if we’re not returning anything, it’s not a function. So, we have a special construct in case we don’t want to return anything: a subroutine. 25 Subroutines A subroutine is exactly like a function, except that: 1. The keyword FUNCTION is replaced by the keyword SUBROUTINE. So, the subroutine has a SUBROUTINE statement and an END SUBROUTINE statement, which are analogous to a function’s FUNCTION statement and END FUNCTION statement. 2. A subroutine has no return type and no return variable. SUBROUTINE input_elements ( & & element, number_of_elements, & & minimum_number_of_elements, & & maximum_number_of_elements) IMPLICIT NONE INTEGER :: number_of_elements INTEGER :: minimum_number_of_elements, & & maximum_number_of_elements REAL,DIMENSION(minimum_number_of_elements: & & maximum_number_of_elements) :: & & element INTEGER :: numelts, e PRINT *, "What are the ", number_of_elements, & & " elements of the array?" READ *, (element(e), & & e = minimum_number_of_elements, & & maximum_number_of_elements) END SUBROUTINE input_elements A subroutine is invoked using a CALL statement: CALL input_elements(element,numelts, & & minnumelements,maxnumelements) Notice that a subroutine has to have side effects to be useful. The general term for functions and subroutines is procedures. That is, a procedure is either a function or a subroutine. 26 Why Do We Like Code Reuse? 1. Bug avoidance: Since we don’t have to retype the procedure from scratch every time we use it, we aren’t constantly making new and exciting typos. 2. Implementation efficiency: We aren’t wasting valuable programming time ($8 - $100s per programmer per hour) on writing commonly used procedures from scratch. 3. Verification: We can test a procedure under every conceivable case, so that we’re confident that it works, and then we don’t have to worry about whether the procedure has bugs when we use it in a new program. Why Do We Like User-Defined Procedures? 1. Code Reuse (see above) 2. Encapsulation: We can write a procedure that packages an important concept (e.g., the cube root). That way, we don’t have to litter our program with cube root calculations. So someone reading our program will be able to tell immediately that, for example, a particular statement has a cube root in it, rather than constantly having to figure out what x ** (1.0 / 3.00) means. 3. Modular Programming: If we make a bunch of encapsulations, we can have our main program simply call a bunch of procedures. That way, it’s easy for someone reading our code to tell what’s going on in the main program, and then to look at individual procedures to see how they work. 27