COMPLEX :: A, B, C
Vid in- och utmatning betraktas komplexa tal som två reella tal, varför eventuell format
specifikation måste ges för två tal. Vid liststyrd inmatning skall de båda
delarna ges med parentes omkring samt med komma och/eller blank emellan.
Ett enkelt exempel på ett program med komplexa tal följer här.
PROGRAM KOMPLEX
REAL :: A, B, D
COMPLEX :: C, C1, C2
A = 3.0
B = 5.0
C1 = CMPLX(A,B) ! Bilda ett komplext tal
C2 = (4.0, 8.0) ! Bilda ett komplext tal
WRITE(*,*) ' C1 = ', C1
WRITE(*,10) C2
10 FORMAT(' C2 = ',F10.3,E12.3)
A = REAL(C1) ! Ta realdelen
B = AIMAG(C1) ! Ta imaginärdelen
C = CONJG(C1) ! Komplexkonjugera
D = ABS(C1) ! Ta beloppet
WRITE(*,20) A, B, C, D
20 FORMAT(' Realdelen av C1 = ', F10.3,/, &
' Imaginärdelen av C1 = ', F12.3,/, &
' Konjugerade C1 = ', 2F10.3,/, &
' Absolutbeloppet av C1 = ', F10.3)
WRITE(*,*) 'Ge nya värden på C1 i list-styrt format'
READ(*,*) C1
WRITE(*,*) 'Ge nya värden på C2 i formaterad form'
READ(*,'(2F10.3)') C2
WRITE(*,*) ' C1 = ', C1
WRITE(*,10) C2
END PROGRAM KOMPLEX
Körning av ovanstående program komplex.f90 på en Sun följer. Notera att
vid list-styrd inmatning måste jag ge det komplexa talet inom parenteser, och vid formaterad
inmatning måste jag placera de båda delarna i rätt kolumner (dvs 1-10 för
realdelen och 11-20 för imaginärdelen), eftersom jag använt formatet
2F10.3.
f90 komplex.f90 a.out C1 = ( 3.0000000, 5.0000000) C2 = 4.000 0.800E+01 Realdelen av C1 = 3.000 Imaginärdelen av C1 = 5.000 Konjugerade C1 = 3.000 -5.000 Absolutbeloppet av C1 = 5.831 Ge nya värden på C1 i list-styrt format (12.0 , 25.0) Inmatade värden Ge nya värden på C2 i formaterad form 7.6 8.3 Inmatade värden C1 = ( 12.0000000, 25.0000000) C2 = 7.600 0.830E+01
Vid flera andra programspråk kan man välja om man vill arbeta i enkel eller dubbel precision för hela programmet, för Fortran gäller att man har full frihet att välja vilka satser som skall utföras i enkel precision och vilka som skall utföras i dubbel precision. Man måste härvid deklarera vilka variabler som skall lagras i dubbel precision med deklarationen
DOUBLE PRECISION :: lista
Vid användning av dubbel precision är det mycket viktigt att komma ihåg att låta
alla aktuella variabler vara i dubbel precision, annars förlorar man lätt noggrannhet,
till exempel om man räknar ut skillnaden D - E, där D är en variabel i dubbel
precision och E är en variabel i enkel precision, så blir resultatet av följande
steg
DOUBLE PRECISION :: D
REAL :: E
...
E = 0.1
D = D - E
att eftersom E lagrar en tiondel binärt i enkel precision, dvs med ett avrundningsfel svarande
mot enkel precision, att även D får ett avrundningsfel svarande mot enkel precision.
Det rätta sättet att skriva är i detta fall, om man vill minska med en tiondel,
DOUBLE PRECISION :: D, E
...
E = 0.1D0
D = D - E
eller enklare
DOUBLE PRECISION :: D
...
D = D - 0.1D0
I den första alternativlösningen är det inte absolut nödvändigt att
ha med D0 i 0.1D0 för att instruera systemet att tiondelen skall beräknas i dubbel
precision, eftersom en intelligent kompilator känner av detta, men det är säkrast.
I den andra alternativlösningen är det mer nödvändigt eftersom det där
är ett sammansatt uttryck. För sådana gäller att uttrycken automatiskt
omvandlas till dubbel precision i enlighet med vissa specifika men krångliga regler, varför
det finns risk att konverteringen till dubbel precision sker efter uträkningen av tiondelen
i stället för vid uträkningen.
Ett enkelt exempel på ett meningslöst program utnyttjande dubbel precision följer.
PROGRAM DUBBEL
DOUBLE PRECISION :: D, E, F
REAL :: X, Y, Z
INTEGER :: K
...
Y = 5.2 - 0.7*F ! Enkel precision
E = 5.2 - 0.7*F ! Enkel precision
D = 5.2D0 - 0.7D0*F ! Dubbel precision (säker)
X = D*F ! Minskad precision
F = K - 20 ! Heltal till dubbel precision
E = D + DBLE(K-20) ! Dubbel precision (säker)
IF ( F < 10.0D0 ) THEN
F = F + 0.3D0
END IF
...
END PROGRAM DUBBEL
Raden 5.2 - 0.7*F är faktiskt intressant. De båda enkelprecisionstalen 5.2 och 0.7 utvidgas till dubbel precision på grund av faktorn F i dubbel precision (men värdet av konstanterna blir eventuellt motsvarande enkel precision) och beräkningen av uttrycket sker i dubbel precision, varefter resultatet lagras i enkelprecisionsvariabeln Y respektive dubbelprecisionsvariabeln E.
Det finns således en viss frihet för kompilatorskrivaren att låta kompilatorn förbättra det program som användaren skrivet, genom att konvertera konstanter av typ decimaltal till dubbel precision i blandade uttryck. Om explicit mellanlagring, i en variabel deklarerad som enkel precision, skall ske är det dock förbjudet att efteråt använda en högre noggrannhet på motsvarande storhet.
Vid konvertering av ett program från enkel till dubbel precision kan det räcka med att glömma konvertera en enda variabel för att nästan hela förbättringen i noggrannhet skall gå förlorad.
I Fortran 90 finns dels standardfunktioner för att undersöka vilken precision som gäller (se Appendix 5, avsnitt 8, där exempelvis PRECISION(X) ger antalet signifikanta siffror hos tal av samma slag som variabeln X), och dels möjligheten att vid deklarationen av en variabel ange hur många signifikanta siffror som minst skall rymmas i flyttal av denna typ (eller detta slag, jämför nedan, avsnitt 10.4). De båda vanliga precisionerna enkel precision (SP) och dubbel precision (DP) på ett IEEE 754 baserat system deklareras med
INTEGER, PARAMETER :: SP = SELECTED_REAL_KIND(6,37) INTEGER, PARAMETER :: DP = SELECTED_REAL_KIND(15,307) REAL(KIND=SP) :: enkelprecisionsvariabler REAL(KIND=DP) :: dubbelprecisionsvariablerOm man vill arbeta med exempelvis minst 14 decimala siffrors noggrannhet och minst en decimal exponent om + 300 så väljer man
INTEGER, PARAMETER :: AP = SELECTED_REAL_KIND(14,300) REAL(KIND=AP) :: arbetsprecisionsvariablerTyvärr måste man nu ge alla flyttalskonstanter med tillägget _AP, till exempel
REAL(KIND=AP) :: PI
PI = 3.141592653589793_AP
medan för de inbyggda funktionerna gäller att dessa är generiska, det vill säga
de känner automatiskt av vilken datatyp (slag) som argument har och väljer då
själva vilket slag som resultatet skall ha (oftast samma som argumentet).
Med denna metod får man i praktiken dubbel precision på datorer baserade på IEEE 754, men enkel precision på datorer som Cray eller datorer baserade på Digital Equipments Alpha-processor, dvs i samtliga fall en verklig precision om ungefär 15 signifikanta siffror.
Den icke-standardiserade datatypen DOUBLE PRECISION COMPLEX eller COMPLEX*16 erhålles helt enkelt genom att i stället skriva deklarationen som till exempel COMPLEX(KIND=2) eller COMPLEX(KIND=16). Detta kan ske på de system som har detta slag definierat på ett lämpligt sätt. Se till exempel Appendix 6 eller 7, varav framgår att detta gäller en del vanliga system. I de fall man inte känner till slags-parameterns aktuella värden ger man i stället följande deklarationer och tilldelningar.
INTEGER, PARAMETER :: AP = SELECTED_REAL_KIND(14,300) COMPLEX(KIND=AP) :: AA ... AA = (0.1_AP, 0.2_AP)
KIND(0) Heltal
KIND(0.0) Flyttal
KIND(.FALSE.) Logisk variabel
KIND("A") Textsträng
Det finns en inbyggd funktion SELECTED_REAL_KIND som ger det slag av typen
REAL vars representation
har (åtminstone) en viss precision och ett visst exponentutrymme. Till exempel så ger
funktionen SELECTED_REAL_KIND(8,70) det slag av REAL som har åtminstone 8 decimala siffrors
noggrannhet och som tillåter belopp mellan 10-70 och 10+70.
För heltal heter den motsvarande funktionen SELECTED_INT_KIND och har naturligtvis bara ett argument. Med till exempel ett val SELECTED_INT_KIND(5) tillåtes alla heltal mellan -99 999 och +99 999. Slaget hos en typ kan tilldelas ett namn.
INTEGER, PARAMETER :: K5 = SELECTED_INT_KIND(5)Detta slag av heltal användes sedan i konstanter enligt
-12345_K5
+1_K5
2_K5
dvs på ett ganska onaturligt sätt, efter värdet följer ett understrykningstecken
samt slagets namn. Däremot kan variabler av det nya heltals-slaget deklareras på ett
trevligare sätt
INTEGER (KIND=K5) :: IVAR
Motsvarande gäller för flyttalsvariabler, om vi först inför en hög
precision LONG med
INTEGER, PARAMETER :: LONG = SELECTED_REAL_KIND(15,99)så får vi ett flyttals-slag med minst femton decimala siffrors noggrannhet och med minst exponent-området från 10-99 till 10+99. Motsvarande konstanter erhålles med
2.6_LONG
12.34567890123456E30_LONG
och variablerna deklareras med
REAL (KIND=LONG) :: LASSE
De gamla typomvandlingarna INT, REAL och CMPLX har utvidgats. Med funktionen
INT(X, KIND = K5)
omvandlas ett flyttal X till ett heltal av slaget K5,
medan om Z är ett komplext flyttal
så ger
REAL(Z, KIND(Z))
omvandling till ett reellt flyttal av samma slag som Z (dvs realdelen av Z).
Dubbel precision finns inte inbyggd i "nya" Fortran 90 på något annat sätt än i "gamla" Fortran 77, men det förutsättes att kompilatorn innehåller stöd för att utnyttja i hårdvaran eventuellt inbyggd dubbel eller fyrdubbel precision, så att man själv enkelt definierar ett lämpligt slag av REAL som DP eller QP. Man kan naturligtvis även använda det gamla begreppet DOUBLE PRECISION.
Anledningen till att man krånglat till det hela på detta sättet är dels att man inte vill ha alltför många obligatoriska precisioner (enkel, dubbel, fyrdubbel; eventuellt för de båda fallen REAL och COMPLEX), dels att det gamla begreppet DOUBLE PRECISION inte innebar någon viss specificerad maskinnoggrannhet. Nu kan man "enkelt" specificera både precisionen och exponentområdet. Ytterligare information om slag ges i Appendix 6, där de olika datatypernas normala slag på både DEC (DEC Station Ultrix) och Sun liksom IBM PC ges vid NAG:s system för Fortran 90. Motsvarande för Cray finns i Appendix 7.
(10.2) Deklarera några variabler av ovanstående flyttalstyp.
Lösning.
(10.3) Deklarera några konstanter av ovanstående flyttalstyp.
Lösning.
TYPE stab_medlem
CHARACTER(LEN=20) :: foernamn, efternamn
INTEGER :: id, avdelning
END TYPE
vilket kan utnyttjas för att beskriva en individ. En kombination av individer kan likaså
bildas.
TYPE(stab_medlem), DIMENSION(100) :: stab
Individer kan refereras som stab(nummer) och ett visst fält
kan refereras som
stab(nummer)%foernamn. Man kan även kapsla definitioner
TYPE bolag
CHARACTER(LEN=20) :: bolagsnamn
TYPE(stab_medlem), DIMENSION(100) :: stab
END TYPE
...
TYPE(bolag), DIMENSION(10) :: flera_bolag
Ett numeriskt mer intressant exempel är en gles matris A med högst hundra element skilda
från noll, som kan deklareras med hjälp av
TYPE NONZERO
REAL VALUE
INTEGER ROW, COLUMN
END TYPE
och
TYPE (NONZERO) :: A(100)
Man får då värdet av A(10) genom att skriva
A(10)%VALUE. Tilldelning kan ske
med exempelvis A(15) = NONZERO(17.0,3,7).
För att kunna använda användardefinierade datatyper i COMMON eller EQUIVALENCE, eller för att se till att två likadana datatyper verkligen betraktas som samma datatyp användes kommandot SEQUENCE, i det senare fallet får dessutom ingen variabel vara deklarerad PRIVATE. Anledningen till dessa extraregler är att normalt är inte ordningen för komponenterna i en struktur fastställd. Kommandot SEQUENCE medför att komponenterna lagras i samma ordning som de uppräknas. Det skall alltid stå direkt efter TYPE kommandot. Följande exempel är hämtat från Überhuber och Meditz (1993) och något modifierat. De påpekar att SEQUENCE inte är något egentligt attribut, eftersom det måste avse alla komponenterna.
TYPE BOK
SEQUENCE
CHARACTER (LEN = 50) :: FOERFATTARE, TITEL, FOERLAG
INTEGER :: UTGIVNINGSAAR, ISBN_NUMMER
REAL :: PRIS
END TYPE BOK