Jag vill även upprepa att under Fortran 77 är alla programenheter i stort sett på samma nivå, även om huvudprogrammet logiskt sett verkar vara överordnat de subrutiner och funktioner som anropas, och att man till och med kan rita upp en anropsgraf som ser ut som ett träd. I verkligheten ligger eventuella BLOCK DATA på en högre nivå, och alla andra programenheter ligger på samma nivå, ur Fortransystemets synpunkt, dock med huvudprogrammet ett litet snäpp högre. Ett undantag är de så kallade satsfunktionerna, vars definitioner kan ligga först i en programenhet (direkt efter deklarationerna), och som är interna för den enheten, och således befinner sig på en logiskt sett lägre nivå. Den normale Fortranprogrammeraren använder dock tyvärr aldrig sådana funktioner.
Ovanstående innebär att alla rutinnamn är på samma logiska nivå, vilket innebär att två olika rutiner i helt olika delar av ett stort program inte får ha samma namn. Ofta innehåller numeriska och grafiska bibliotek tusentals rutiner (med högst sex tecken i namnen under gamla Fortran), varför det är en påtaglig risk för namnkonflikt. Detta var en sak som de gamla satsfunktionerna kunde avhjälpa, eftersom de är interna för respektive enhet, och därför kan finnas under samma namn men med olika uppgift i olika programenheter. Nackdelen är att de bara kan hantera vad som ryms på en programrad (men de kan anropa varandra på så sätt att en senare kan anropa en tidigare, men ej tvärtom).
Nu har interna funktioner och subrutiner tillkommet. Dessa definieras sist i respektive programenhet (dock ej i BLOCK DATA) efter det nya kommandot CONTAINS och före END. Ett internt subprogram har tillgång till samma variabler som enheten den tillhör, inklusive möjligheten att anropa dess andra interna subprogram. Det skrives i övrigt som ett vanligt subprogram, men får ej kapslas, dvs ej ha egna interna funktioner eller subrutiner.
Vanliga subrutiner och funktioner kallas liksom tidigare externa subrutiner och externa funktioner, men nu finns det en större anledning till den benämningen än tidigare. Numera finns ju även interna subprogram, tidigare fanns bara inbyggda (intrinsic) som alternativ. För övrigt har antalet inbyggda funktioner ökat mycket kraftigt.
I variabeldeklarationer till subprogram har för argumenten tillkommet möjligheten att ange om en variabel är invariabel, utvariabel, eller båda samtidigt. Detta anges med INTENT som kan vara IN, OUT eller INOUT. Om IN gäller så kan det verkliga argumentet vid anropet vara ett uttryck som X+Y eller SIN(X) eller en konstant som 37, eftersom ett värde skall överföras till subprogrammet men ej åter till anropande enhet. Variabeln får i detta fall ej tilldelas något nytt värde i subprogrammet. Om OUT gäller så måste däremot det verkliga argumentet vara en variabel. Vid inträde i subprogrammet anses då variabeln som odefinierad. Det tredje fallet täcker båda möjligheterna, ett värde in, ett annat (eller eventuellt samma) ut. Även då måste naturligtvis det verkliga argumentet vara en variabel. Om argumentet har ett pekar-attribut så får INTENT ej sättas. Implementeringen av INTENT är dock ännu ej fullständig.
Den ena användningen för den nya programenheten MODULE är att ta hand om globala data, och den ersätter då BLOCK DATA, den andra är att paketera nya datatyper.
PROGRAM programnamnoch måste avslutas med
ENDeller
END PROGRAMeller
END PROGRAM programnamnObservera att ordet PROGRAM måste vara med i END satsen om programnamnet är det!
Det kan vara lämpligt att låta huvudprogrammet vara ett mycket kort program, som bara består av viss grundläggande inläsning samt anrop av en del av de olika subrutiner och funktioner som tillsammans med huvudprogrammet bildar programmet.
Antalet subrutiner i ett program kan vara noll. Det kan finnas externa och interna subrutiner, dessutom kan de fem i Fortran inbyggda (eng. intrinsic) subrutinerna användas.
Antalet funktioner i ett program kan vara noll. Det kan finnas externa och interna funktioner, samt satsfunktioner, dessutom kan de många i Fortran inbyggda (eng. intrinsic) funktionerna användas.
Moduler illustreras här med tre exempel, nämligen en för byte av två variabler (av samma men varierande typ), en för intervallaritmetik och slutligen en som bestämmer vissa standardvärden, bland annat vilken flyttalsprecision (snarare maskinprecision) som skall användas.
Man flyttar över allt som rör SWAP till en modul, vilken sedan kan användas i huvudprogrammet med satsen USE modulnamnet. Notera att i modulens INTERFACE skall den speciella satsen MODULE PROCEDURE användas för att undvika att rutinerna specificeras både i INTERFACE och i CONTAINS. Vid användning måste naturligtvis både modulen och huvudprogrammet länkas ihop, till exempel med satsen f90 del1.f90 del2.f90. Här följer närmast modulen:
MODULE BO INTERFACE SWAP MODULE PROCEDURE SWAP_R, SWAP_I, SWAP_C END INTERFACE CONTAINS SUBROUTINE SWAP_R(A,B) IMPLICIT NONE REAL, INTENT (INOUT) :: A, B REAL :: TEMP TEMP = A ; A = B ; B = TEMP END SUBROUTINE SWAP_R SUBROUTINE SWAP_I(A,B) IMPLICIT NONE INTEGER, INTENT (INOUT) :: A, B INTEGER :: TEMP TEMP = A ; A = B ; B = TEMP END SUBROUTINE SWAP_I SUBROUTINE SWAP_C(A,B) IMPLICIT NONE CHARACTER, INTENT (INOUT) :: A, B CHARACTER :: TEMP TEMP = A ; A = B ; B = TEMP END SUBROUTINE SWAP_C END MODULE BOHär följer så huvudprogrammet, vilket nu är rensat på all ointressant information om SWAP.
PROGRAM SWAP_HUVUD USE BO IMPLICIT NONE INTEGER :: I, J, K, L REAL :: A, B, X, Y CHARACTER :: C, D, E, F I = 1 ; J = 2 ; K = 100 ; L = 200 A = 7.1 ; B = 10.9 ; X = 11.1; Y = 17.0 C = 'a' ; D = 'b' ; E = '1' ; F = '"' WRITE (*,*) I, J, K, L, A, B, X, Y, C, D, E, F CALL SWAP(I,J) ; CALL SWAP(K,L) CALL SWAP(A,B) ; CALL SWAP(X,Y) CALL SWAP(C,D) ; CALL SWAP(E,F) WRITE (*,*) I, J, K, L, A, B, X, Y, C, D, E, F END
MODULE INTERVALL_ARITMETIK TYPE INTERVALL REAL LAEGRE, OEVRE END TYPE INTERVALL INTERFACE OPERATOR (+) MODULE PROCEDURE ADDERA_INTERVALL END INTERFACE INTERFACE OPERATOR (-) MODULE PROCEDURE SUBTRAHERA_INTERVALL END INTERFACE INTERFACE OPERATOR (*) MODULE PROCEDURE MULTIPLICERA_INTERVALL END INTERFACE INTERFACE OPERATOR (/) MODULE PROCEDURE DIVIDERA_INTERVALL END INTERFACE CONTAINS FUNCTION ADDERA_INTERVALL(A,B) TYPE(INTERVALL), INTENT(IN) :: A, B TYPE(INTERVALL) :: ADDERA_INTERVALL ADDERA_INTERVALL%LAEGRE = A%LAEGRE + B%LAEGRE ADDERA_INTERVALL%OEVRE = A%OEVRE + B%OEVRE END FUNCTION ADDERA_INTERVALL FUNCTION SUBTRAHERA_INTERVALL(A,B) TYPE(INTERVALL), INTENT(IN) :: A, B TYPE(INTERVALL) :: SUBTRAHERA_INTERVALL SUBTRAHERA_INTERVALL%LAEGRE = A%LAEGRE - B%OEVRE SUBTRAHERA_INTERVALL%OEVRE = A%OEVRE - B%LAEGRE END FUNCTION SUBTRAHERA_INTERVALL FUNCTION MULTIPLICERA_INTERVALL(A,B) ! POSITIVA TAL FÖRUTSÄTTES TYPE(INTERVALL), INTENT(IN) :: A, B TYPE(INTERVALL) :: MULTIPLICERA_INTERVALL MULTIPLICERA_INTERVALL%LAEGRE = & A%LAEGRE * B%LAEGRE MULTIPLICERA_INTERVALL%OEVRE = & A%OEVRE * B%OEVRE END FUNCTION MULTIPLICERA_INTERVALL FUNCTION DIVIDERA_INTERVALL(A,B) ! POSITIVA TAL FÖRUTSÄTTES TYPE(INTERVALL), INTENT(IN) :: A, B TYPE(INTERVALL) :: DIVIDERA_INTERVALL DIVIDERA_INTERVALL%LAEGRE = A%LAEGRE / B%OEVRE DIVIDERA_INTERVALL%OEVRE = A%OEVRE / B%LAEGRE END FUNCTION DIVIDERA_INTERVALL END MODULE INTERVALL_ARITMETIKVid kompilering av ovanstående skapas en fil intervall_aritmetik.mod eller intervall_aritmetik.M som innehåller en intressant modifierad version av koden ovan. Ett program som vill utnyttja detta paket inkluderar satsen USE INTERVALL_ARITMETIK först bland specifikationssatserna, då finns direkt både datatypen INTERVALL och de fyra räknesätten på denna typ tillgängliga. I en del fall är det önskvärt att bara inkludera en del av faciliteterna i en modul, detta sker med ONLY enligt nedan.
USE modul_namn, ONLY : lista_över_utvalda_rutinerFöljande är ett exempel på ett mycket enkelt huvudprogram för test av intervallaritmetiken. Det ligger på filen intervall.f90 eller intv.f90.
USE INTERVALL_ARITMETIK IMPLICIT NONE TYPE (INTERVALL) :: A, B, C, D, E, F A%LAEGRE = 6.9 A%OEVRE = 7.1 B%LAEGRE = 10.9 B%OEVRE = 11.1 WRITE (*,*) A, B C = A + B D = A - B E = A * B F = A / B WRITE (*,*) C, D WRITE (*,*) E, F ENDKörning av detta program på Sun-dator med NAG:s Fortran 90 kompilator följer.
f90 intervall.f90 intervall_aritmetik.f90 intervall.f90: intervall_aritmetik.f90: a.out 6.9000001 7.0999999 10.8999996 11.1000004 17.7999992 18.2000008 -4.2000003 -3.7999997 75.2099991 78.8100052 0.6216216 0.6513762Med Suns:s egen SunSoft kompilator använder jag i stället
f90 -c intervall_aritmetik.f90 f90 intervall.f90 intervall_aritmetik.f90På Digitals system hittar jag det riktiga kommandot som är
f90 intervall_aritmetik.f90 intervall.f90dvs modulen skall komma före användningen! Detta fungerar även på SunSoft och NAG!
I ovanstående exempel har betydelsen av bland andra tecknet + generaliserats till att gälla även intervall. Fortran 90 innehåller en spärr mot att definiera om betydelsen av + på vanliga variabler.
(14.2) Komplettera modulen så att paketet utför lämplig felhantering vid division
med ett intervall som innehåller noll.
Lösning.
(14.3) Komplettera även så att hänsyn tas till det lokala avrundningsfelet vid
operationen.
Lösning.
Det följande programmet är ett enkelt exempel på användning av en sådan modul.
MODULE START IMPLICIT NONE INTEGER, PARAMETER :: AP = SELECTED_REAL_KIND(14, 300) REAL (KIND=AP), PARAMETER :: ONE_TENTH = 0.1_AP END MODULE START PROGRAM TEST_START USE START REAL (KIND=AP) :: PI, X, Y, Z PI = 4.0_AP*ATAN(1.0_AP) X = 0.1 Y = 10*(X - ONE_TENTH) Z = 10*ONE_TENTH - 1.0_AP WRITE(*,*) X, ONE_TENTH WRITE(*,*) Y, Z END PROGRAM TEST_STARTUtmatningen visar dels att det hela fungerar, dels vikten av att explicit ange dubbel precision eller motsvarande vid konstanter som inte är exakta i "kort" precision. Tyvärr har jag inte hittat något bra sätt att ge pi ett värde i arbetsprecisionen redan i modulen.
0.1000000014901161 0.1000000000000000 1.4901161138336505E-08 0.0000000000000000E+000
PRIVATE PUBLIC :: VAR1blir samtliga variabler utom VAR1 lokala, medan VAR1 blir globalt tillgänglig. Notera att båda dessa begrepp antingen kan ges som kommandon, till exempel
INTEGER :: IVAR PRIVATE :: IVAReller som attribut
INTEGER, PRIVATE :: IVARoch motsvarande för PUBLIC.
Denna nya programenhet består av följande element
BLOCK DATA namn Deklarationer Datasatser ENDEtt enkelt exempel följer, där det första blocket även sparas. Det är inte nödvändigt att alla variabler i ett sådant block har initierats, i exemplet nedan har således inte heltalsvariabeln ANTAL getts något startvärde.
BLOCK DATA START INTEGER :: VEK(10), ANTAL CHARACTER*29 :: ALFA COMMON /BLOCK1/ VEK, ANTAL COMMON /BLOCK2/ ALFA SAVE /BLOCK1/ DATA VEK / 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 / DATA ALFA /'ABCDEFGHIJKLMNOPQRSTUVWXYZÅÄÖ'/ END
INCLUDE 'cls.f' INCLUDE '../fortran90/cls.f' INCLUDE '/mailocal/lab/numt/TANA70/cls.f'Denna sats fanns i flera utvidgningar till Fortran 77 och användes främst för att lägga in identiska kopior av COMMON-block i flera subrutiner och funktioner. Som tidigare påpekats är det ju väsentligt att speciellt namnade COMMON-block är identiska varje gång de uppträder.
Under Fortran 90 kan man med fördel i stället använda moduler. En fördel med att inkludera programfiler med INCLUDE är att det kan röra sig om ofullständiga programavsnitt som uppträder på flera ställen, medan moduler måste följa vissa syntax-regler. Observera dock att sedan avsnittet inkluderats måste resultatet följa Fortrans syntaxregler. I exemplet ovan, som inkluderar en hel subrutin, måste därför INCLUDE satsen antingen ligga först eller efter ett END (svarande mot en programenhet, ej bara END DO eller END IF).
Däremot så är inte ordningen mellan satserna i en programenhet godtycklig. Den första satsen skall vara PROGRAM, SUBROUTINE, FUNCTION, BLOCK DATA eller MODULE, den sista satsen skall vara END. Det gäller dock, för att uppnå kompatibilitet bakåt mot gamla Fortranversioner, att satsen PROGRAM i ett huvudprogram är frivillig.
En programenhet skall vara uppbyggd på följande sätt
Specifikation av programenheten USE modul IMPLICIT NONE Övriga IMPLICIT satser INTERFACE Deklarationer Satsfunktioner Exekverbar del CONTAINS Interna subrutiner och funktioner ENDDet enda som är obligatoriskt är END-satsen, ett helt korrekt huvudprogram (och därmed även program) kan bestå av enbart satsen END. Det programmet utför naturligtvis ingenting, det kan sägas vara det enklast möjliga Fortran-programmet, och kan naturligtvis användas för test. Jag prövade det under NAG:s kompilator på Sun och fick inga felutskrifter och ett körbart program på 73 728 bytes, medan det under Sun:s Fortran 77 kompilator gav hela 188 416 bytes.
FORMAT-satser kan finnas var som helst mellan USE och CONTAINS, men det är lämpligt att samla dom i början eller slutet eller, vilket är vanligast, i direkt samband med den första läs- eller skrivsats som använder den.
Likaså kan DATA-satser och PARAMETER-satser placeras nästan var som helst, men jag rekommenderar varmt att de placeras bland deklarationerna (eftersom de ej är exekverbara).
Även eventuella ENTRY-satser kan placeras ganska fritt, men här rekommenderar jag inplacering bland de exekverbara satserna.
ENTRY-satsen ger en möjlighet till en alternativ ingång i en subrutin eller funktion. Jag avråder dock bestämt från dess användning.