An introduction to FORTRAN 77 (G77).
FORTRAN (FORmula TRANslation) is a programming language designed specifically for scientists and engineers. For over 50 years FORTRAN has been used for such projects as the design of bridges and aeroplane structures, it is used for factory automation control, for storm drainage design, analysis of scientific data and so on. FORTRAN continues to be used today, in particular to realize complicated scientific calculations that have to be executed at high speed, as for example in meteorology, or super-computer benchmarking. Intel has a FORTRAN compiler in their free development suite for the latest Windows releases; it may be fully integrated in the Visual Studio environment.
FORTRAN 77 was released in 1977, a time where computer data input was done using punch cards. This imposed strict limitations, such as a maximum of characters per line (actually 80 characters) and strict column indentation rules, what means that you cannot simply write your text anywhere in the line, but instead the F77 standard tells us where (i.e. at which line column) and how a specific information has to be written. This way to write a computer program is called fixed format coding.
G77 (formerly part of the GNU compiler collection) is a FORTRAN 77 with several extensions (mostly implementing FORTRAN 90 features). In particular, it allows you to use free format coding, i.e. you are free where within the line you write specific information (similar as you would do in Pascal, C, Perl, Python, etc). The two FORTRAN formats are not compatible! Thus using G77 to compile a free format program requires the special command line parameter -ffree-form to be specified. Note, that the sample programs in this text use the standard FORTRAN 77 fixed format.
This text is not a complete FORTRAN 77 tutorial. It's primarily 3 simple program examples, showing how variable declarations, calculation of expressions, arrays, decision making (IF statements), loops (DO statements), reading from the console (keyboard input), writing to the console (screen output), writing to a text file, and subroutines are coded in FORTRAN 77. The whole with comments and explanations. You may see it as a quick look-up for common FORTRAN statements, or as template code for your own first FORTRAN programs for G77. On which platform does this (very) old FORTRAN 77 run on, you may ask. It runs on DOS (cf. my tutorial Installing and running Gnu FORTRAN on FreeDOS), I suppose also on the first Windows versions, maybe even up to Windows 2000 (I did not try...). And the interest of using this obsolete programming language? Beside nostalgic considerations, there is one really good reason: There are thousands of FORTRAN 77 programs, mostly scientific applications, available; you can find lots of them on the Internet, download the code (and adapt it to your needs).
Program 1: Determine the date of Easter for a given year.
This program, taken from the book "Interactive FORTRAN 77 - A Hands on Approach", asks the user to enter a year from the keyboard, determines the date of Easter for this year (if you are interested in the algorithm to calculate the year of Easter, search the Internet) and outputs the date to the screen. The next paragraph shows the code (click the following link to download the source code of the 3 sample programs). If you have a basic knowledge of programming, you probably understand a more or less great part of the code. Detailed explanations will follow.
C A simple program to calculate the date of Easter
C From "Interactive Fortran 77 - A Hands on Approach"
PROGRAM EASTER
INTEGER YEAR, METCYC, CENTRY, ERROR1, ERROR2, DAY
INTEGER EPACT, LUNA
WRITE (*, "(A29, $)") ' Calculate Easter for year = '
READ *, YEAR
C CALCULATING THE YEAR IN THE 19 YEAR METONIC CYCLE-METCYC
METCYC = MOD(YEAR, 19) + 1
IF (YEAR.LE.1582) THEN
DAY = (5 * YEAR) / 4
EPACT = MOD(11 * METCYC - 4, 30) + 1
ELSE
C CALCULATING THE CENTURY-CENTRY
CENTRY = (YEAR / 100) + 1
C ACCOUNTING FOR ARITHMETIC INACCURACIES
C IGNORES LEAP YEARS ETC.
ERROR1 = (3 * CENTRY / 4) - 12
ERROR2 = ((8 * CENTRY + 5) / 25) - 5
C LOCATING SUNDAY
DAY = (5 * YEAR / 4) - ERROR1 - 10
C LOCATING THE EPACT (FULL MOON)
EPACT = MOD(11 * METCYC + 20 + ERROR2 - ERROR1, 30)
IF (EPACT.LT.0) EPACT = 30 + EPACT
IF ((EPACT.EQ.25.AND.METCYC.GT.11).OR.EPACT.EQ.24) THEN
EPACT = EPACT + 1
ENDIF
ENDIF
C FINDING THE FULL MOON
LUNA = 44 - EPACT
IF(LUNA.LT.21) LUNA = LUNA + 30
C LOCATING EASTER SUNDAY
LUNA = LUNA + 7 -(MOD(DAY + LUNA, 7))
C LOCATING THE CORRECT MONTH
IF (LUNA.GT.31) THEN
LUNA = LUNA - 31
PRINT *, 'For the year', YEAR
PRINT *, 'Easter falls on April', LUNA
ELSE
PRINT *, 'For the year', YEAR
PRINT *, 'Easter falls on March', LUNA
ENDIF
END
Fixed format coding rules.
First, note that FORTRAN 77 does not distinguish between upper and lower characters and so you are free to choose your own style of writing FORTRAN code. Older FORTRAN 77 programs are mostly entirely written in capital letters. Second, you can immediately see that this program uses fixed format, the FORTRAN code starting at column 7.
Here an overview of the FORTRAN 77 fixed format coding rules:
Columns | Usage | Notes |
---|---|---|
1 | A C in this column indicates a comment, a space indicates that the line contains FORTRAN code | Normally you can use any other non blank character to indicate a comment. Some programmers prefer to use an exclamation mark (!). It should also be possible to comment a part of a line that contains FORTRAN code by placing the comment (preceded by an exclamation mark, for example) behind the instructions. |
2 - 5 | Reserved for placing a numerical label. This allows you to request an unconditional jump to this line from anywhere inside the same program, function or subroutine. | It is obvious that this "jumping around" (cf. GOTO in BASIC) makes it difficult to follow the instruction flow of the program and should be avoided! |
6 | Reserved for placing a character, usually a + sign, which designates a continuation of the previous line. | You will often encounter a situation in which the code is too long to fit in the instruction columns of a single line. In this case, you'll need to break it into several lines, each continuation line requiring the character + in column 6. |
7 - 73 | Space where you write the FORTRAN 77 instructions. | Many modern compilers can read the code beyond the 73th character, but it is recommendable to break long instructions in several lines (with a + in column 6). Instructions may be placed anywhere within theses column space, thus you can indent IF or DO blocks, the same way as you would do using a modern programming language. |
74 - 80 | Not considered by the compiler (may be line numbers?). | Just leave them blank... |
Program blocks.
Our example starts with the line PROGRAM EASTER and ends with the line END. This defines the main program block. If the program includes user defined subroutines or functions, they will have their own blocks, coded behind (not before, as in Pascal) the main program block.
Variable declaration.
Before you may use a variable, this variable has to be declared, i.e. you have to assign a given data type to it. Variable declaration is
done at the beginning of the program (or subroutine/function) block; the syntax is similar as in C. There are two declaration lines in our sample program:
INTEGER YEAR, METCYC, CENTRY, ERROR1, ERROR2, DAY
INTEGER EPACT, LUNA
The variables YEAR, METCYC, CENTRY, ERROR1, ERROR2, DAY, EPACT, LUNA are all declared as belonging to the data type INTEGER.
Here a table with the FORTRAN 77 types:
Type | Meaning | Values | Notes |
---|---|---|---|
INTEGER | Number without decimal digits | -231 to 231 - 1 | Similar to the Pascal INTEGER type |
BYTE | Small INTEGER | -128 to 127 | Similar to the Pascal BYTE type |
REAL | Number with decimal digits | approx. between 1.2 · 10-38 and 3.4 · 1038, positive and negative |
Similar to the Pascal REAL type |
DOUBLE PRECISION | Large REAL | approx. between 2.2 · 10308 and 1.8 · 10308, positive and negative |
Similar to the Pascal DOUBLE type |
CHARACTER | One character | One alphanumeric character | Similar to the Pascal CHAR type; as in Pascal, constants are placed between single quotes |
CHARACTER*N | Several characters | N alphanumeric characters | Similar to the Pascal STRING[N] type; as in Pascal, string constants are placed between single quotes |
LOGICAL | False or true condition | .FALSE. or .TRUE. | Similar to the Pascal BOOLEAN type. |
COMPLEX | Complex number | Two REAL numbers, real and imaginary part |
COMPLEX constants are written as (R, I), where R is the real, and I the imaginary part |
DOUBLE COMPLEX | Large complex number | Two DOUBLE PRECISION numbers, real and imaginary part |
DOUBLE COMPLEX constants are written as (R, I), where R is the real, and I the imaginary part |
FORTRAN 77 allows you to assign implicitly a particular data type to all variables whose symbolic names start with a common character. By default variables starting with the letters I - N are implicitly declared as INTEGER. It's not really recommendable to use this feature and you should declare all your variables explicitly at the beginning of the program. To be sure that there aren't any implicitly declared variables, add the instruction IMPLICIT NONE immediately after the PROGRAM statement.
Variable definitions and assignments.
Assigning a value to a variable is done using the = operator (as in most programming languages; not in Pascal where := is used). The variable is at the left side of the equality sign, the value (or the variable, or expression giving a value) on the right side. If a variable has to be assigned an initial value (variable initialization), then the assignment is called variable definition and should be done at the beginning of the program, after the variable declaration part.
The value assigned to a variable can be a constant value, the value of another variable or the result of an expression. Examples from our program:
DAY = (5 * YEAR) / 4
LUNA = LUNA - 31
EPACT = MOD(11 * METCYC - 4, 30) + 1
All elements on the right side of the equality sign actually are expressions (cf. further down in the text). Be sure that the type of the value
to be assigned is the same (and in FORTRAN "the same" really means "the same") as the type of the variable it is assigned to. Otherwise, you either get a
compilation error, or a result that will not be what you expect, and in such a case, it may take some time to find the mistake in your source code (we'll see further
down in the text how conversions, internally done by FORTRAN, may lead to "surprising" results of expressions).
Constants definitions.
Even though there aren't any constant definitions in the 3 sample programs, lets have a look at how they are coded in FORTRAN 77. From the point of view program
structure, constant definition should be done between variable declaration and variable definition parts. Constants, called parameters in
FORTRAN, are symbolic names which contain a value that cannot be changed during the run of the program. Their definition should contain two parts: 1. their type
declaration (just as for variables); 2. the constant value assignment that is done using the instruction PARAMETER. Example:
REAL PI, E
PARAMETER(PI=3.2415927, E=2.7182818)
Arithmetic expressions.
Arithmetic expressions may be composed of numerical variables and parameters, arithmetic operators and functions. G77 arithmetic operators are the usual +, -, *, /, plus ** (exponentiation = power). Intrinsic mathematical functions include ABS, SQRT, SIN, COS, TAN, ASIN, ACOS, ATAN, EXP, LOG, LOG10, MOD (remainder of an integer division), MIN and MAX (with two or more arguments; minimum resp. maximum). INT and REAL may be used for conversion to integer, resp. real.
An important fact to know about G77 (FORTRAN in general?) is that the data type of an expression containing an integer is an integer! Thus, the expression 1/2 is not 0.25 as expected, but 0. To get the correct real result, you'll have to use 1.0/2.0. The same applies to the expression AVG = SUM / N. With AVG being real, SUM and N being integers, the expression will give an integer result! To get the correct result, you'll have to code AVG = REAL(SUM) / REAL(N); in the case where AVG and SUM are real, the correct expression would be AVG = SUM / REAL(N).
This is also true for functions. Normally a function has to get arguments of a precise data type and there are different functions for different data types. As an example the G77 function to calculate the absolute value of a number:
Function | Argument | Return type | Standard |
---|---|---|---|
ABS(N) | INTEGER, REAL, COMPLEX | same as argument, except COMPLEX argument returns REAL | FORTAN 77 and later with GNU extensions overloads |
IABS(N) | INTEGER | INTEGER | FORTAN 77 and later |
DABS(N) | DOUBLE | DOUBLE | FORTAN 77 and later |
CABS(N) | COMPLEX | REAL | FORTAN 77 and later |
CDABS(N) | DOUBLE COMPLEX | DOUBLE | GNU extension |
ZABS(N) | DOUBLE COMPLEX | DOUBLE | GNU extension |
Some examples of arithmetic expressions (and their assignment to a variable) in our sample program:
DAY = (5 * YEAR) / 4
CENTRY = (YEAR / 100) + 1
EPACT = MOD(11 * METCYC - 4, 30) + 1
All variables and constants in these expressions, as well as the variables, that the value of the expression is assigned to, are integers, thus the result is an integer, too. In fact, with the first two expressions, we want to calculate the integer part of a division of integers (operator DIV in Pascal), and the third expression uses the function MOD to calculate the remainder of a division of two integers (11 * METCYC - 4 and 30).
Logical expressions.
Logical expressions may be composed of logical variables and parameters, logical and conditional operators and functions. G77 includes the following conditional operators:
Operator | Example | Description |
---|---|---|
.EQ. | X .EQ. Y | true if X is equal to Y (X = Y) |
.NE. | X .NE. Y | true if X is not equal to Y (X ≠ Y) |
.LT. | X .LT. Y | true if X is less than Y (X < Y) |
.LE. | X .LE. Y | true if X is less than or equal to Y (X ≤ Y) |
.GT. | X .GT. Y | true if X is greater than Y (X > Y) |
.GE. | X .GE. Y | true if X is greater than or equal to Y (X ≥ Y) |
The logical operators .AND., .OR., and .NOT. act on logical expressions as in other programming languages.
Examples of logical expressions in our sample program:
EPACT.LT.0
LUNA.GT.31
(EPACT.EQ.25.AND.METCYC.GT.11).OR.EPACT.EQ.24
where the first two expressions are true if EPACT is negative, resp. if LUNA is greater than 31; the third expression is true if either EPACT = 26 and METCYC > 11, or
EPACT = 24.
Conditional statements.
Conditional statements (IF statements) test if a logical expression (such as a comparison) is true. The logical expression has to be placed between round brackets (as in most programming languages and as a difference with Pascal where there are no brackets needed). There are three forms of the IF statement in G77:
- IF (logical expression) ...
The statement (and only this one) following the logical expression is executed if the logical expression is true. Example from our program:
IF(LUNA.LT.21) LUNA = LUNA + 30
- IF (logical expression) THEN
... ENDIF
The statements between THEN and ENDIF form a block that is executed if the logical expression is true. Example from our program:
IF ((EPACT.EQ.25.AND.METCYC.GT.11).OR.EPACT.EQ.24) THEN
EPACT = EPACT + 1
ENDIF - IF (logical expression) THEN ...
ELSE ... ENDIF
The statements between THEN and ELSE form a block that is executed if the logical expression is true, and the statements between ELSE and ENDIF form another bloc, that is executed if the logical expression is false. Example from our program:
IF (LUNA.GT.31) THEN
LUNA = LUNA - 31
PRINT *, 'For the year', YEAR
PRINT *, 'Easter falls on April', LUNA
ELSE
PRINT *, 'For the year', YEAR
PRINT *, 'Easter falls on March', LUNA
ENDIF
Keyboard input and screen output.
Unformatted input from the keyboard, more exactly from standard input, is done using one of the following
statements
READ (*, *) item1, item2, ...
READ *, item1, item2, ...
Example from our sample program:
READ *, YEAR
In the first statement, the first asterisk refer to the standard input unit (unit 5), the second one tells the compiler that no special formatting is used for the input, i.e. that unformatted input is used. The second statement is a simplification of the first one, the unit being omitted, standard input being used automatically.
Unformatted output to the screen, more exactly to standard output, is done using one of the following
statements
WRITE (*, *) item1, item2, ...
PRINT *, item1, item2, ...
Example from our sample program:
PRINT *, 'For the year', YEAR
With the WRITE statement, the first asterisk refer to the standard output unit (unit 6), the second one tells the compiler that no special formatting is used for the output, i.e. that unformatted output is used. The PRINT statement is a simplification of the WRITE statement, the unit being omitted, standard output being the only unit that may be used with this statement.
Formatted output to the screen, more exactly to standard output is traditionally done by specifying a
numerical label, and this label is then used to define the output format using the FORMAT statement.
WRITE (*, label) item1, item2, ...
label FORMAT(item1-format, item2-format, ...)
Remember that in fixed format FORTRAN, labels are placed in columns 2 to 5!
In practice, the FORMAT statement isn't used anymore. Instead the output format is included within the WRITE
statement. Here is how this form of the formatted output statement looks like:
WRITE (*, "(item1-format, item2-format, ...)") item1, item2, ...
Example from our sample program:
WRITE (*, "(A29, $)") ' Calculate Easter for year = '
where A29 (the format for the string literal) means 29 alphanumeric characters (with string values, the field width is often omitted;
in this case the actual length of the string is used), and $ is a special formatting symbol, corresponding to
advance="no" in later FORTRAN versions. We use it here to keep the cursor in the same line (no CR+LF) in order to enter the year in the
same line as the text telling us to do so.
Formatting characters overview.
Format | Usage | Description | Example |
---|---|---|---|
A | Aw | alphanumeric field with a width of w | A5: "Aly" will be displayed as " Aly" |
I | Iw | integer numbers field with a width of w | I5: "99" will be displayed as " 99" |
F | Fw.d | real numbers fixed point field with a width of w and display of d decimal digits | F6.2: "2.5" will be displayed as " 2.50", "3.14159" will be displayed as "3.1416" |
E D |
Ew.d Dw.d |
real resp. double precision numbers exponent notation field with a width of w and display of d decimal digits | E8.1: " 0.025" will be displayed as " 0.3E-01" |
X | nX | horizontal skip (space); if n is omitted n = 1 | I3 2X I3: "126" followed by "621" will be displayed as "126 621" |
/ | / | vertical skip (new line); each slash corresponds to one newline | I3 // I3: "126" followed by "621" will be displayed as "126", followed by an empty line, and finally "621" a further line down |
To note that, as a difference with C, WRITE is by default followed by a CR+LF. To avoid this, use the special formatting symbol $, as described above.
The screenshot below shows the output of our first sample program.
Program 2: Determine the date of Easter for a given period of time.
The program calculates the date of Easter for a period from year 1 to year 2. The list with the Easter dates is written to a text file. The program uses the the program EASTER from "Interactive FORTRAN 77 - A Hands on Approach" (our sample program 1) as subroutine to calculate the date for one given year. The new FORTRAN elements introduced with this sample are loops (DO statements), subroutines and output to a text file.
C A simple program to calculate the date of Easter for a given period of time (year1 to year2)
C The list with the Easter dates is written to the file easter.txt
C Uses the program EASTER from "Interactive Fortran 77 - A Hands on Approach" as subroutine
PROGRAM EASTER2
INTEGER YEAR, YEAR1, YEAR2, MONTH, LUNA
CHARACTER*5 SMONTH
WRITE (*, *) 'Calculate Easter for a given period:'
WRITE (*, "(A14, $)") ' First year = '
READ *, YEAR1
WRITE (*, "(A14, $)") ' Last year = '
READ *, YEAR2
IF (YEAR2.GE.YEAR1) THEN
OPEN(UNIT=1, FILE='easter.txt')
WRITE (1, "(A19, 1X, I4, 1X, A2, 1X, I4)")
+ 'Date of Easter from', YEAR1, 'to', YEAR2
WRITE (1, *)
DO YEAR = YEAR1, YEAR2
CALL EASTER(YEAR, MONTH, LUNA)
IF (MONTH.EQ.3) THEN
SMONTH = 'March'
ELSE
SMONTH = 'April'
ENDIF
WRITE (1, "(I4, 5X, A5, 1X, I2)") YEAR, SMONTH, LUNA
ENDDO
CLOSE(UNIT=1)
WRITE (*, *) 'List with Easter dates in file: easter.txt'
ENDIF
END
C *************************************************
C * Subroutine to calculate Easter for a given year
C *************************************************
SUBROUTINE EASTER(YEAR, MONTH, LUNA)
INTEGER YEAR, METCYC, CENTRY, ERROR1, ERROR2, DAY, MONTH
INTEGER EPACT, LUNA
C CALCULATING THE YEAR IN THE 19 YEAR METONIC CYCLE-METCYC
METCYC = MOD(YEAR, 19) + 1
IF (YEAR.LE.1582) THEN
DAY = (5 * YEAR) / 4
EPACT = MOD(11 * METCYC - 4, 30) + 1
ELSE
C CALCULATING THE CENTURY-CENTRY
CENTRY = (YEAR / 100) + 1
C ACCOUNTING FOR ARITHMETIC INACCURACIES
C IGNORES LEAP YEARS ETC.
ERROR1 = (3 * CENTRY / 4) - 12
ERROR2 = ((8 * CENTRY + 5) / 25) - 5
C LOCATING SUNDAY
DAY = (5 * YEAR / 4) - ERROR1 - 10
C LOCATING THE EPACT (FULL MOON)
EPACT = MOD(11 * METCYC + 20 + ERROR2 - ERROR1, 30)
IF (EPACT.LT.0) EPACT = 30 + EPACT
IF ((EPACT.EQ.25.AND.METCYC.GT.11).OR.EPACT.EQ.24) THEN
EPACT = EPACT + 1
ENDIF
ENDIF
C FINDING THE FULL MOON
LUNA = 44 - EPACT
IF(LUNA.LT.21) LUNA = LUNA + 30
C LOCATING EASTER SUNDAY
LUNA = LUNA + 7 -(MOD(DAY + LUNA, 7))
C LOCATING THE CORRECT MONTH
IF (LUNA.GT.31) THEN
LUNA = LUNA - 31
MONTH = 4
ELSE
MONTH = 3
ENDIF
END
The program consists of two blocs: the main program (that asks for the years, then, for each year, calls the subroutine to calculate the Easter date, and finally writes this date to the text file) and a subroutine called "EASTER" (that does the calculation of the month and day for a given year).
Declaring and calling subroutines.
A FORTRAN subroutine is similar to a Pascal procedure, some of its arguments being used to calculate or modify some others. Subroutines are declared using the
reserved word SUBROUTINE, followed by the subroutine name and its arguments between round brackets. As a difference with Pascal or C, the
data type of the arguments is not indicated within the subroutine declaration statement, but the arguments have to be declared as variables in the subroutine bloc.
Example from our sample program:
SUBROUTINE EASTER(YEAR, MONTH, LUNA)
INTEGER YEAR, MONTH, LUNA
The code included within a subroutine is executed by using the instruction CALL, followed by the subroutine name and its arguments between
round brackets.
Example from our sample program:
CALL EASTER(YEAR, MONTH, LUNA)
Note that the variables used in the CALL statement are variables of the main program, whereas those used in the SUBROUTINE statement are local variables of the subroutine bloc. In this example, they have the same name as the main program variables that, of course, is not necessarily the case. Important to know: All arguments of a FORTRAN subroutine are passed by reference! This means that it is not the value of a variable, but a pointer to its address location that is passed. As a consequence, any changes to a local argument variable will change the corresponding variable in the main program. That's a major difference with, for example Pascal, where arguments of a procedure may be passed either by value (input arguments only), or by reference (input and/or output arguments).
I found in some FORTRAN books that procedures have to contain a RETURN statement. As my program works correctly, this seems not to be mandatory and the final subroutine END seems to implicitly include the return to the main program (or eventually other calling subroutine).
To note that beside subroutines, FORTRAN also allows to define custom functions. As in Pascal, they can only return a single value. Thus, if the subprogram calculates two or more values (as in our case), or doesn't calculate any value, a subroutine has to be used.
Loop statements.
For each year within the considered period, the program has to calculate the Easter date and write this date to a text file. This repetitive execution of a given bloc of statements is called a loop.
Traditionally, a FORTRAN 77 DO-loop has the following syntax:
DO label counter = start, end, step
...
label CONTINUE
This is similar to the FOR statement in other programming languages. The DO-bloc is executed with the value of the counter variable (that must be an integer and must not be changed within the bloc) varying from the start value to the end value, being incremented by the step value during each iteration (if the step value is omitted, +1 is used).
An alternative to the DO-loop with label is to use a DO ... ENDDO construct. This construct is not part
of ANSI FORTRAN 77, but is implemented by most FORTRAN 77 compilers, as is the case for G77. Here an example of this construct from our sample program:
DO YEAR = YEAR1, YEAR2
...
ENDDO
The loop above is a simple counter loop. A conditional loop is a loop where the statements of its bloc are
executed as long as a given logical expression is true. In ANSI FORTRAN 77, this kind of loop is coded using IF and
GOTO:
label IF (logical expression) THEN
...
GOTO label
ENDIF
Lots of FORTRAN 77 compilers offer an extension that allows the usage of DO WHILE or WHILE ... DO constructs to implement conditional loops.
It's obvious that with these statements, except if you want an infinite loop, the logical expression has to be modified within the bloc.
File input-output.
In FORTRAN each file is associated with a unit number, an integer between 1 and 99. Remember that unit 5 and 6 are reserved for standard input resp. standard output.
Before being able to read from or write to a file, you must open it. When you have done with the file, you should close it.
The basic syntax for opening a file is one of the following:
OPEN(UNIT=unit-number, FILE=filename)
OPEN(unit-number, FILE=filename)
Example from our sample program:
OPEN(UNIT=1, FILE='easter.txt')
Besides UNIT and FILE, the instruction may contain several other specifiers, among others IOSTAT = ios, where "ios" is the I/O status identifier (integer variable), that upon return is zero if the statement was successful and returns a non-zero value otherwise, and STATUS = sta, where "sta" is one of NEW, OLD, and SCRATCH (file that is created and that will be deleted when it is closed).
The basic syntax for closing a file is one of the following:
CLOSE(UNIT=unit-number)
CLOSE(unit-number)
Example from our sample program:
CLOSE(UNIT=1)
As with OPEN several other specifiers (in particular IOSTAT) may be part of the instruction.
After a file has been opened (and until it is closed), you may read data from it, or write data to it. The general format with all possible specifiers looks like
this:
READ ([UNIT =] UNIT, [FMT =] format, IOSTAT = ios, ERR = error-label, END = eof-label)
WRITE ([UNIT =] UNIT, [FMT =] format, IOSTAT = ios, ERR = label1)
where "format" is the output-format as described further up in the text ("*" for unformatted output), where "ios" is the I/O status identifier, where "error-label" is
a label where to jump to if there is a file error (the ERR specifier can also be used when opening or closing a file), and where "eof-label"
is a label where to jump to when the end-of-file is reached.
Examples from our program:
WRITE (1, "(A19, 1X, I4, 1X, A2, 1X, I4)") 'Date of Easter from', YEAR1, 'to', YEAR2
WRITE (1, *)
Note: In the sample program, the first of these instructions spans over two lines (usage of a "continuation character" in column 6). The second instruction writes nothing but a CR+LF (empty line); this had better been done using the "/" format symbol in the write operation before.
The screenshot shows a part of the program output, opened in Notepad (on my Windows 10).
Program 3: Determine the frequency of the date of Easter for a given period of time.
The program calculates the frequency of the date of Easter for a period from year 1 to year 2. The list with the Easter date frequencies is written to a text file. The program uses the program EASTER from "Interactive FORTRAN 77 - A Hands on Approach" (our sample program 1) as subroutine to calculate the date for one given year, and the subroutine SORT to sort the frequencies, descending by their values. Beside nested DO statements and the usage of the EXIT instruction to exit from a DO-bloc, the only new FORTRAN element introduced with this program is the usage of one-dimensional arrays.
C A simple program to calculate the frequency of the date of Easter for a given period of time (year1 to year2)
C The frequency values are written to the file easterfq.txt
C Uses the program EASTER from "Interactive Fortran 77 - A Hands on Approach" as subroutine
PROGRAM EASTER3
INTEGER YEAR, YEAR1, YEAR2, MONTH, LUNA, I, J
CHARACTER*5 SMONTH
INTEGER FREQ(61), FREQX(61)
REAL FRQ
DO I = 1, 61
FREQ(I) = 0
ENDDO
WRITE (*, *) 'Calculate frequency of Easter for a given period:'
WRITE (*, "(A14, $)") ' First year = '
READ *, YEAR1
WRITE (*, "(A14, $)") ' Last year = '
READ *, YEAR2
IF (YEAR2.GE.YEAR1) THEN
OPEN(UNIT=1, FILE='easterfq.txt')
WRITE (1, "(A19, 1X, I4, 1X, A2, 1X, I4)")
+ 'Frequency of Easter for a period from ', YEAR1, 'to', YEAR2
WRITE (1, *)
DO YEAR = YEAR1, YEAR2
CALL EASTER(YEAR, MONTH, LUNA)
IF (MONTH.EQ.3) THEN
FREQ(LUNA) = FREQ(LUNA) + 1
ELSE
FREQ(LUNA + 31) = FREQ(LUNA + 31) + 1
ENDIF
ENDDO
DO I = 1, 61
FREQX(I) = I
ENDDO
CALL FREQSORT (61, FREQ, FREQX)
DO K = 1, 61
IF (FREQ(K).EQ.0) EXIT
I = FREQX(K)
IF (I.LE.31) THEN
SMONTH = 'March'
J = I
ELSE
SMONTH = 'April'
J = I - 31
ENDIF
FRQ = 100 * (REAL(FREQ(K)) / REAL((YEAR2 - YEAR1 + 1)))
WRITE (1, "(A5, 1X, I2, 5X, F5.2, A1)") SMONTH, J, FRQ, '%'
ENDDO
CLOSE(UNIT=1)
WRITE (*, *) 'Easter frequency values in file: easterfr.txt'
ENDIF
END
C *************************************************
C * Subroutine to calculate Easter for a given year
C *************************************************
SUBROUTINE EASTER(YEAR, MONTH, LUNA)
INTEGER YEAR, METCYC, CENTRY, ERROR1, ERROR2, DAY, MONTH
INTEGER EPACT, LUNA
C CALCULATING THE YEAR IN THE 19 YEAR METONIC CYCLE-METCYC
METCYC = MOD(YEAR, 19) + 1
IF (YEAR.LE.1582) THEN
DAY = (5 * YEAR) / 4
EPACT = MOD(11 * METCYC - 4, 30) + 1
ELSE
C CALCULATING THE CENTURY-CENTRY
CENTRY = (YEAR / 100) + 1
C ACCOUNTING FOR ARITHMETIC INACCURACIES
C IGNORES LEAP YEARS ETC.
ERROR1 = (3 * CENTRY / 4) - 12
ERROR2 = ((8 * CENTRY + 5) / 25) - 5
C LOCATING SUNDAY
DAY = (5 * YEAR / 4) - ERROR1 - 10
C LOCATING THE EPACT (FULL MOON)
EPACT = MOD(11 * METCYC + 20 + ERROR2 - ERROR1, 30)
IF (EPACT.LT.0) EPACT = 30 + EPACT
IF ((EPACT.EQ.25.AND.METCYC.GT.11).OR.EPACT.EQ.24) THEN
EPACT = EPACT + 1
ENDIF
ENDIF
C FINDING THE FULL MOON
LUNA = 44 - EPACT
IF(LUNA.LT.21) LUNA = LUNA + 30
C LOCATING EASTER SUNDAY
LUNA = LUNA + 7 -(MOD(DAY + LUNA, 7))
C LOCATING THE CORRECT MONTH
IF (LUNA.GT.31) THEN
LUNA = LUNA - 31
MONTH = 4
ELSE
MONTH = 3
ENDIF
END
C ***********************************************
C * Subroutine to sort the Easter frequency array
C ***********************************************
SUBROUTINE FREQSORT(N, ARRAY, INDEXES)
INTEGER N, ARRAY(N), INDEXES(N)
INTEGER F, X, I, J
DO I = 1, N - 1
DO J = I + 1, N
IF (ARRAY(I).LT.Array(J)) THEN
F = ARRAY(I)
ARRAY(I) = ARRAY(J)
ARRAY(J) = F
X = INDEXES(I)
INDEXES(I) = INDEXES(J)
INDEXES(J) = X
ENDIF
ENDDO
ENDDO
DO I = 1, N - 1
DO J = I + 1, N
IF (ARRAY(I).EQ.Array(J).AND.INDEXES(I).GT.INDEXES(J)) THEN
F = ARRAY(I)
ARRAY(I) = ARRAY(J)
ARRAY(J) = F
X = INDEXES(I)
INDEXES(I) = INDEXES(J)
INDEXES(J) = X
ENDIF
ENDDO
ENDDO
END
The program consists of three blocs: 1. The main program (that asks for the years, calls a subroutine to calculate the Easter date for each year, incrementing the corresponding per date counter (actually an array element). When all dates are calculated, another subroutine is called to sort the per date counter array, and finally, the counts are transformed into frequencies and written to the text file. 2. The subroutine "EASTER" (that does the calculation of the month and day for a given year; exactly the same as in the preceding program). 3. The subroutine "SORT" that sorts the array containing the per date counters.
Declaring an array and accessing its elements.
A one-dimensional array is just a linear sequence of elements stored consecutively in memory. Such an array may be declared using one of the following statements:
data-type array-variable(array-length)
data-type array-variable(first-element-index:last-element-index)
Important to know that an array declared by indicating its length is a one-based array, i.e. the first element has the index 1 (and not
0 as in most programming languages). Array declaration example from our sample program:
INTEGER FREQ(61), FREQX(61)
This declares two arrays with 61 integer elements, the first element having an index of 1, the last one an index of 61.
Each element of an array may be seen as a separate variable. A given element is accessed by the array name followed by the index in round
brackets (that's also a difference with most programming languages that use square brackets in this case). Example from the sample program:
DO I = 1, 61
FREQ(I) = 0
ENDDO
This statement sets all array elements to 0 (initialization of the array).
Some explanations concerning sample program 3.
The aim of the program is to create a list showing, for the time period entered by th user, how many times Easter felt on a given date. As the dates to be considered range from 1st March to 30th April, we can use an array with 61 elements, one element for each day of this period and the elements being used as "Easter counters".
First, we initialize the array, setting all counters to zero, as shown in the previous paragraph. Then for each year, we increment by 1 the counter corresponding
to that date. For March, we can use the day returned by the EASTER subroutine itself as index of the counter array. For April, we have to adjust the index value: As
March has 31 days, the value of the index is equal to the day returned by the subroutine plus 31. Here is the corr. code:
DO YEAR = YEAR1, YEAR2
CALL EASTER(YEAR, MONTH, LUNA)
IF (MONTH.EQ.3) THEN
FREQ(LUNA) = FREQ(LUNA) + 1
ELSE
FREQ(LUNA + 31) = FREQ(LUNA + 31) + 1
ENDIF
ENDDO
The list should however not be ordered by date, but by the frequency of Easter, those days where Easter felt the most often being at the top of the list. This requires
to sort the counter array. However, just sorting the array elements is not enough! As the position of an element in the array actually
corresponds to a given date, we have to create a second array and sorting it the same way than we sort the counts. What has this other array to contain? The different
dates, you will probably say, and that is right. But, there is another possibility. As there is a direct correspondence between the counter array indexes and the dates,
our second array can actually contain these indexes. That's why, before calling the SORT subroutine, we initialize the second array as follows:
DO I = 1, 61
FREQX(I) = I
ENDDO
When the sort is done (the elements in FREQX having been sorted the same way as the elements in FREQ), we have everything in the order we want. All that remains to
do is to transform the index values in FREQX to dates. This follows the same logic as the one used when storing the Easter counts into FREQ; here is the
corresponding code:
DO K = 1, 61
IF (FREQ(K).EQ.0) EXIT
I = FREQX(K)
IF (I.LE.31) THEN
SMONTH = 'March'
J = I
ELSE
SMONTH = 'April'
J = I - 31
ENDIF
FRQ = 100 * (REAL(FREQ(K)) / REAL((YEAR2 - YEAR1 + 1)))
WRITE (1, "(A5, 1X, I2, 5X, F5.2, A1)") SMONTH, J, FRQ, '%'
ENDDO
Note the usage of the EXIT statement, that terminates the DO loop (resuming execution with the instruction following ENDDO). This statement is executed if the count for a given date is zero; as the counts are sorted in descending order, this means that with the first count being zero, the loop is terminated, in other words, only frequencies (these are calculated from the counts) greater than zero will be written to the list.
Also note the usage of the REAL function in the expression to calculates the frequency. If you do not use it, all frequencies will be zero!
The "SORT" subroutine is a simple bubble sort. As already said, the important thing here, is not only to sort the array with the counts, but also the array with the indexes.
If you look at the subroutine, you can see that the code to implement the bubble sort is followed by a portion of code, that seems to be another bubble sort. And it is another bubble sort. I let it as a little knowledge test to the reader to find out, what this second sort is for. Here is a hint: It is not mandatory, but it ensures that the list looks "prettier" if it is there than if it wasn't. If you don't find an answer, just rebuild the program without this portion of code and compare the result with the original one...
And, to be complete, here is the screenshot with a part of the program output, opened in Notepad (on my Windows 10).
If you find this text helpful, please, support me and this website by signing my guestbook.