De inbyggda funktionerna är generiska, dvs datatypen hos argumentet anger vilken rutin som skall väljas. Under Fortran 66 var man tvungen att skriva SIN(X) om X var REAL, DSIN(X) om X var DOUBLE PRECISION och slutligen CSIN(X) om X var COMPLEX. Från Fortran 77 fungerar SIN(X) i alla tre fallen. Om funktionsnamnet användes som argument vid funktions- eller subrutinanrop måste däremot det explicita namnet anges.
Vanliga enkla funktioner diskuterades redan i avsnitt 3.13. Vi skall därför här diskutera funktioner utan argument, fältvärda funktioner och rekursiva funktioner. Vidare skall vi ta upp "avsikten" hos olika argument, frivilliga argument och argument med nyckelord. Användning av funktioner som argument skall likaså diskuteras. Vi börjar med det senare.
REAL FUNCTION NOLL(F, A, B, EPS)
IMPLICIT NONE
! Beräknar nollställe till en funktion f(x) om
! a < b och f(a) > 0 och f(b) < 0 och eps > 0.
REAL :: F, A, B, EPS
REAL :: V, H, MITT
V = A
H = B
DO
IF ( H - V >= EPS ) THEN
MITT = 0.5*(V+H)
IF ( F(MITT) > 0.0 ) THEN
V = MITT
ELSE
H = MITT
END IF
CYCLE ! Ny iteration behövs.
END IF
EXIT ! Konvergens har erhållits.
END DO
NOLL = 0.5*(V+H)
RETURN
END FUNCTION NOLL
Funktionen ovan fungerar så att först testas om vänster- och högerpunkterna
ligger tillräckligt nära varandra, i så fall utförs inte
IF-satsen och
den "eviga" DO-slingan avbrytes med EXIT.
Ett slutligt nollställe beräknas som medelvärdet
av vänster- och högerpunkterna. Om punkterna ej ligger tillräckligt nära
beräknas en mittpunkt, som väljes som ny vänster- eller högerpunkt, och
en ny iteration begäres med CYCLE. Funktionen täcker som synes inte alla fall, och
svarar inte alltid så tidigt som möjligt. Anledningen är att vi sökt göra
den så enkel som möjligt för att illustrera hur funktioner med funktioner som
argument fungerar. Ett par utvidgningar föreslås i övningarna nedan.
Den funktion F(X) som användes som argument till funktionen NOLL måste också finnas, och då varken som intern funktion eller som satsfunktion, utan som en extern funktion, eller som en inbyggd funktion. Ett exempel på en extern funktion ges nedan, med namnet G.
REAL FUNCTION G(X)
IMPLICIT NONE
REAL :: X
G = COS(X) - LOG(X)
RETURN
END FUNCTION G
För att använda dessa funktioner kräves även ett
huvudprogram.
Härvid
är det väsentligt, på grund av Fortrans separatkompilering, att informera systemet
om att funktionen G(X) är en extern funktion, vilket sker med satsen
EXTERNAL G.
PROGRAM NOLLSTAELLE
IMPLICIT NONE
REAL :: NOLL, A, B, G, EPS, Y
EXTERNAL G
A = 1.0
B = 2.0
EPS = 2.0E-7
Y = NOLL(G, A, B, EPS)
WRITE(*,*) Y
STOP
END PROGRAM NOLLSTAELLE
Om funktionen i stället är en inbyggd funktion användes satsen
INTRINSIC funktion.
I exemplet ovan passar det bra att byta ut satsen EXTERNAL G
mot INTRINSIC COS och satsen Y = NOLL(G, A, B, EPS) mot
Y = NOLL(COS, A, B, EPS), varvid programmet räknar ut att pi/2
är ett
nollställe till cosinus. I detta fall behövs naturligtvis inte den
externa funktionen
G(X). Notera även att den inbyggda funktionen ej skall typdeklareras.
(5.2) Modifiera funktionen NOLL till att klara även andra
teckenkombinationer på f(a) och f(b).
Lösning.
(5.3) Modifiera funktionen NOLL till att använda en mer avancerad
algoritm än intervallhalvering, till exempel regula falsi.
Kommentar.
Som exempel har jag skrivet en enkel funktion utan argument. För att få ut något resultat ur den har jag valt att använda tidsrutinen i Fortran 90 (se Appendix 5). I heltalsvektorn VALUES lagras år, datum och tidpunkt, vilka jag summerar med fältadderaren SUM för att få något som påminner om ett slumptal. Anropet av tidsrutinen sker med nyckelordsargument, vilket förklaras senare.
PROGRAM TEST_INGET_ARGUMENT
IMPLICIT NONE
REAL :: NOLL_ARG, Y
Y = NOLL_ARG()
WRITE(*,*) Y
STOP
END PROGRAM TEST_INGET_ARGUMENT
REAL FUNCTION NOLL_ARG()
IMPLICIT NONE
INTEGER, DIMENSION(8) :: VALUES
CALL DATE_AND_TIME(VALUES=VALUES)
NOLL_ARG = SUM(VALUES)
RETURN
END FUNCTION NOLL_ARG
RECURSIVE FUNCTION FAKULTET(N) RESULT (FAK_RESULTAT)
IMPLICIT NONE
INTEGER, INTENT(IN) :: N
INTEGER :: FAK_RESULTAT
IF ( N <= 1 ) THEN
FAK_RESULTAT = 1
ELSE
FAK_RESULTAT = N * FAKULTET(N-1)
END IF
END FUNCTION FAKULTET
RECURSIVE FUNCTION FIBONACCI(N) RESULT (FIBO_RESULTAT)
IMPLICIT NONE
INTEGER, INTENT(IN) :: N
INTEGER :: FIBO_RESULTAT
IF ( N <= 2 ) THEN
FIBO_RESULTAT = 1
ELSE
FIBO_RESULTAT = FIBONACCI(N-1) + FIBONACCI(N-2)
END IF
END FUNCTION FIBONACCI
Anledningen till att ovanstående beräkning av Fibonacci-talen blir så ineffektiv
är att anrop med ett visst värde på N genererar två anrop av rutinen, som
i sin tur generar fyra, osv. Gamla värden (anrop) återutnyttjas ej!
En intressantare användning av rekursiv teknik är beräkning av exponentialfunktionen av en matris. I stället för det omedelbara uttrycket med successiv multiplikation med en matris kan man använda en rekursiv metod där man plockar ut 2-potenser för att optimera beräkningen. Rekursion är vidare utmärkt för att koda adaptiva algoritmer, se Övning (5.5) nedan.
En annan viktig användning av det nya begreppet resultat-variabel är vid fält-värda funktioner, då det är lätt att deklarera denna variabel att lagra funktionens värde(n). Det är faktiskt kombinationen rekursivitet och fält som tvingat fram det nya begreppet.
I programmet ovan användes det nya begreppet "avsikt" eller INTENT, vilket förklaras i avsnitt 5.2.1.5.
(5.5) Skriv en adaptiv rutin för kvadratur, dvs beräkning av den bestämda integralen
över ett visst intervall.
Lösning.
Fältvärda funktioner kräver den tidigare diskuterade nyheten, nämligen ett explicit gränssnitt (INTERFACE) i den anropande programenheten. Gränssnittet består i princip av deklarationerna i funktionen, se exemplet nedan. Gränssnitt kräves vid flera olika tillfällen och är faktiskt det svåraste i Fortran 90. Se vidare avsnittet 11.2.
Nedanstående enkla funktion ger som resultat en vektor med de första åtta potenserna av det skalära argumentet.
PROGRAM TEST_FAELTVAERD_FUNKTION
IMPLICIT NONE
INTERFACE ! Detta är gränssnittet för den
! fältvärda funktionen POTENS(X).
FUNCTION POTENS(X) RESULT(FAELT)
REAL :: X
REAL, DIMENSION(8) :: FAELT
END FUNCTION POTENS
END INTERFACE
REAL :: X
REAL, DIMENSION(8) :: Y
X = 2.0
Y = POTENS(X)
WRITE(*,*) Y
STOP
END PROGRAM TEST_FAELTVAERD_FUNKTION
FUNCTION POTENS(X) RESULT(FAELT)
IMPLICIT NONE
REAL :: X
REAL, DIMENSION(8) :: FAELT
INTEGER :: I
DO I = 1, 8
FAELT(I) = X**I
END DO
RETURN
END FUNCTION POTENS
Vi kan ganska enkelt justera ovanstående funktion till att klara godtyckliga potenser, där
ordningen ges endast som indata i huvudprogrammet. Dimensionen överföres i nästa
exempel automatiskt med argumentet, en flyttalsvektor, och dimensionen för resultatet sättes
lika med denna dimension. Det går naturligtvis minst lika bra att överföra dimensionen
med ett vanligt heltalsargument.
Det är inte nödvändigt att här låta argumentet vara en flyttalsvektor. Man kan även klara sig med ett skalärt argument, men då måste man använda pekare vid deklarationen av fältet, se sektion 4.3.2.
PROGRAM NY_TEST_FAELTVAERD_FUNKTION
IMPLICIT NONE
INTERFACE
FUNCTION POTENS(X) RESULT(FAELT)
REAL, DIMENSION(:) :: X
REAL, DIMENSION(SIZE(X)) :: FAELT
END FUNCTION POTENS
END INTERFACE
REAL, DIMENSION(:), ALLOCATABLE :: X
REAL, DIMENSION(:), ALLOCATABLE :: Y
INTEGER :: DIM
WRITE(*,*) " Ge ordning "
READ(*,*) DIM
ALLOCATE(X(DIM))
ALLOCATE(Y(DIM))
X = 2.0 ! Detta är en vektoroperation.
Y = POTENS(X)
WRITE(*,*) Y
STOP
END PROGRAM NY_TEST_FAELTVAERD_FUNKTION
FUNCTION POTENS(X) RESULT(FAELT)
IMPLICIT NONE
REAL, DIMENSION(:) :: X
REAL, DIMENSION(SIZE(X)) :: FAELT
INTEGER :: I
DO I = 1, SIZE(X)
FAELT(I) = X(I)**I
END DO
RETURN
END FUNCTION POTENS
Om argumentet har ett pekar-attribut POINTER så får INTENT ej sättas.
PROGRAM TEST_AV_AVSIKT
IMPLICIT NONE
INTERFACE
FUNCTION FUN(X,Y,Z) RESULT(UT)
REAL :: UT
REAL, INTENT(IN) :: X
REAL, INTENT(OUT) :: Y
REAL, INTENT(INOUT) :: Z
END FUNCTION FUN
END INTERFACE
REAL :: Y, Z
Z = 12.0
WRITE(*,*) Z
Y = FUN(1.0,Y,Z)
WRITE(*,*) Y, Z
STOP
END PROGRAM TEST_AV_AVSIKT
FUNCTION FUN(X,Y,Z) RESULT (UT)
! Varning! Denna funktion har sido-effekter, vilket är
! olämpligt. Sidoeffekterna är här för att illustrera
! avsikt eller INTENT.
IMPLICIT NONE
REAL, INTENT(IN) :: X
REAL, INTENT(OUT) :: Y
REAL, INTENT(INOUT) :: Z
REAL :: UT
Y = SIN(X) + COS(Z)
Z = SIN(X) - COS(Z)
UT = Y + Z
RETURN
END FUNCTION FUN
Om man ovan exempelvis byter ut Y = FUN(1.0,Y,Z) mot Y = FUN(1.0,Y,3.0) klagar NAG:s Fortran 90
kompilator på att det ej getts något ut-argument på plats 3.
Användningen av nyckelord och underförstådda argument är ett av de fall då ett explicit gränssnitt INTERFACE erfordras. Jag ger därför här ett fullständigt exempel. Som nyckelord användes de formella parametrarna i gränssnittet, vilka ej behöver ha samma namn som de i den verkliga subrutinen. Dessa nyckelord skall ej deklareras i anropande programenhet.
IMPLICIT NONE
INTERFACE
FUNCTION SUMMA (A, B, N) RESULT (UT)
REAL :: UT
INTEGER, INTENT (IN) :: N
REAL, INTENT(OUT) :: A
REAL, INTENT(IN), OPTIONAL :: B
END FUNCTION SUMMA
END INTERFACE
REAL :: X, Y
Y = SUMMA(X,100.0,20) ! Normalt anrop
WRITE(*,*) X, Y
Y = SUMMA(B=10.0,N=50,A=X) ! Anrop med nyckelord
WRITE(*,*) X, Y
Y = SUMMA(B=10.0,A=X,N=100) ! Anrop med nyckelord
WRITE(*,*) X, Y
Y = SUMMA(N=100,A=X) ! Anrop med nyckelord
! och ett skönsvärde.
WRITE(*,*) X, Y
END
FUNCTION SUMMA(A,B,N) RESULT (UT)
IMPLICIT NONE
REAL :: A, UT
REAL, OPTIONAL, INTENT (IN) :: B
INTEGER :: N
REAL :: TEMP_B
IF (PRESENT(B)) THEN
TEMP_B = B
ELSE
TEMP_B = 20.0
END IF
A = TEMP_B + N
UT = A + TEMP_B + N
RETURN
END
Notera att IMPLICIT NONE för huvudprogrammet ej verkar i funktionen
SUMMA, varför denna
har kompletterats med det kommandot och deklaration av de ingående variablerna.
Den inbyggda funktionen PRESENT är sann om dess argument finns med i anropet, och falsk om det inte finns med. I det senare fallet användes i stället skönsvärdet (eng. the default value).
Körning på UNIX sker med
f90 program.f90
a.out
1.2000000E+02 2.4000000E+02
60.0000000 1.2000000E+02
1.1000000E+02 2.2000000E+02
1.2000000E+02 2.4000000E+02
Gränssnittet INTERFACE placeras lämpligen i en
modul,
så att användaren
inte behöver bry sig. Gränssnitten blir ett naturligt komplement till rutinbiblioteken,
Fortran 90 söker automatiskt efter moduler i aktuell filkatalog, eventuella filkataloger
i I-listan, samt /usr/local/lib/f90. Begreppet I-lista
förklaras i Appendix 6. Om man glömmer
INTERFACE eller har ett felaktigt sådant erhålles ofta felet "Segmentation error".
Detta fel kan dock även erhållas vid ett i princip korrekt INTERFACE, men då
man glömt att allokera något av de ingående fälten i den anropande programenheten.
Notera att om en utmatningsvariabel anges som OPTIONAL och INTENT (OUT) så måste den vara med i anropslistan om programmet vid exekveringen lägger ut ett värde på denna variabel. Man måste därför i sitt program använda test med PRESENT av aktuell variabel, och endast om den är med i anropet använda den för tilldelning, för att på så sätt få den önskade valfriheten om man bara ibland vill ha ut en viss variabel.
Grundprincipen för de inbyggda generiska funktionerna tål dock att upprepas, dvs att datatypen hos argumentet anger vilken rutin som skall väljas. Under Fortran 66 var man tvungen att skriva SIN(X) om X var REAL, DSIN(X) om X var DOUBLE PRECISION och slutligen CSIN(X) om X var COMPLEX för att få resultat av rätt typ och noggrannhet. Från Fortran 77 fungerar SIN(X) i alla tre fallen.
Jag ger här ett fullständigt exempel på en generisk subrutin, nämligen en rutin SWAP(A,B) som byter plats på värdena hos variablerna A och B, utnyttjande olika underliggande rutiner beroende på om de båda argumenten är av typ REAL, INTEGER eller CHARACTER.
Subrutinen finns således i detta fall i tre varianter, nämligen för flyttal, heltal och textsträng (med längden ett). De är skrivna på vanligt sätt och heter SWAP_R, SWAP_I respektive SWAP_C. Samtliga är dessutom med i huvudprogrammets INTERFACE med sina deklarationer, men detta gränssnitt har namnet SWAP. När man i huvudprogrammet vill utnyttja någon av subrutinerna använder man enbart namnet SWAP och argument av viss typ. Systemet väljer då bland de tre tillgängliga funktionerna ut den som svarar mot argumenten i anropet. Om dessa båda argument är av olika typ blir det ett fel.
PROGRAM SWAP_HUVUD
IMPLICIT NONE
INTEGER :: I, J, K, L
REAL :: A, B, X, Y
CHARACTER :: C, D, E, F
INTERFACE SWAP
SUBROUTINE SWAP_R(A,B)
REAL, INTENT (INOUT) :: A, B
END SUBROUTINE SWAP_R
SUBROUTINE SWAP_I(A,B)
INTEGER, INTENT (INOUT) :: A, B
END SUBROUTINE SWAP_I
SUBROUTINE SWAP_C(A,B)
CHARACTER, INTENT (INOUT) :: A, B
END SUBROUTINE SWAP_C
END INTERFACE
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
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
Ovanstående fungerar utmärkt, men användaren är inte så
glad åt
att behöva släpa med all information om SWAP och dess olika
varianter i sitt program.
Lösningen är att flytta över allt som rör SWAP till en
modul, vilken sedan
kan användas i huvudprogrammet med satsen USE modulnamnet. Vi återkommer till detta
i kapitel 14.
För de lokala funktionerna och subrutinerna gäller att de ej kan användas som argument vid anrop av externa funktioner eller subrutiner (eller andra lokala).
IMPLICIT NONE
REAL :: X, Y, PI, COT, NCOT
! Efter deklarationer kan satsfunktioner placeras.
Y(X) = X + 2.0*X - 3.0*X**2
COT(X) = 1.0/TAN(X)
NCOT(X) = COT(PI*X)/PI
! Nu följer de exekverbara satserna.
PI = 3.141592654
READ(*,*) X
WRITE(*,*) X, Y(X), COT(X), NCOT(X)
END
Här är satsen PI = 3.14159264 den första exekverbara satsen, men den borde nog
hellre ha placerats bland deklarationerna utnyttjande PARAMETER satsen eller attributet. Denna
form ger dock en god information om att satsfunktionerna utnyttjar argument (här X) och konstanter
(här PI) som tilldelas senare, men satsfunktioner som definierats tidigare! De kan naturligtvis
även använda konstanter och variabler som initierats redan bland deklarationerna.
Fördelarna gentemot en extern funktion är dels att lokala variabler delas med den aktuella programenheten, dels att funktionsnamnet blir lokalt (vilket innebär minskad risk för namnkonflikt).
Ett enkelt exempel följer, nämligen en omskrivning av satsfunktionerna ovan. Notera att de tre interna funktionerna Y, COT och NCOT nu ej får deklareras på rad 3, den med REAL :: X, PI.
PROGRAM INTERN
IMPLICIT NONE
REAL :: X, PI
! Nu följer de exekverbara satserna.
PI = 3.141592654
READ(*,*) X
WRITE(*,*) X, Y(X), COT(X), NCOT(X)
CONTAINS
! Här placeras interna funktioner och subrutiner.
FUNCTION Y(X)
REAL :: Y
REAL, INTENT(IN) :: X
Y = X + 2.0*X - 3.0*X**2
END FUNCTION Y
FUNCTION COT(X)
REAL :: COT
REAL, INTENT(IN) :: X
COT = 1.0/TAN(X)
END FUNCTION COT
FUNCTION NCOT(X)
REAL :: NCOT
REAL, INTENT(IN) :: X
NCOT = COT(PI*X)/PI
END FUNCTION NCOT
END PROGRAM INTERN
I verkligheten kan de olika interna funktionerna och subrutinerna vara mycket komplicerade enheter
med alla satser i Fortran, de kan dock ej i sin tur innehålla interna funktioner eller subrutiner.
OBS! Var försiktig vid deklaration av interna funktioner och subrutiner. De deklareras i samband med att de införes, de skall ej ges någon "egen" deklaration.
Ett eventuellt IMPLICIT NONE i den överordnade programenheten gäller automatiskt även alla interna funktioner och subrutiner, liksom naturligtvis för eventuella satsfunktioner.
(5.7) Skriv det gränssnitt (interface) som behövs i anropande
rutin för att kunna
använda ovanstående integrationsrutin.
Lösning.