10. Ytterligare datatyper

10.0 Inledning

I Fortran 77 finns de båda datatyperna COMPLEX för komplexa tal och DOUBLE PRECISION för dubbel precision hos vanliga flyttal. Dessa båda datatyper saknas i de flesta andra programspråk, men de finns naturligtvis kvar i Fortran 90. I en del implementationer av Fortran 77 finns även kombinationen komplex dubbelprecision, men denna har aldrig standardiserats.

10.1 Komplexa tal (COMPLEX)

Ett komplext tal karakteriseras av att det består av en realdel och en imaginärdel. I Fortran begränsar man sig till det fallet att dessa båda delar är flyttal (dvs ej heltal). Man deklarerar motsvarande variabler med
        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

10.2 Dubbel precision (DOUBLE PRECISION)

På en normal dator finns numera oftast flyttalssystem enligt IEEE 754, vilket innebär att enkel precision har cirka 7 decimala siffrors noggrannhet, men i dubbel precision cirka 16 decimala siffror. På en dator som Cray gäller i stället 13 siffror i enkel precision och 28 i dubbel precision.

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.

10.3 Det nya precisionsbegreppet

Problemet med tidigare versioner av Fortran var att enkel precision på en dator kunde svara mot en högre noggrannhet än dubbel precision på en annan dator, och att den icke standardiserade datatypen DOUBLE PRECISION COMPLEX eller COMPLEX*16 ej fanns på alla system. Denna senare datatyp finns det ett stort behov av på normala maskiner med enbart cirka 7 siffrors noggrannhet i enkel precision.

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)       :: dubbelprecisionsvariabler 
Om 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)       :: arbetsprecisionsvariabler
Tyvä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)

10.4 Olika precision eller slag (KIND)

Eftersom det finns variabler av olika typer så finns det nu också en inbyggd funktion som känner av aktuell typ, nämligen funktionen KIND(X) som känner av typen hos X. Denna funktion användes även för deltyper eller "slag".
    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.

Övningar.

(10.1) Deklarera en flyttalstyp som svarar mot dubbel precision på IBM och enkel precision på Cray.
Lösning.

(10.2) Deklarera några variabler av ovanstående flyttalstyp.
Lösning.

(10.3) Deklarera några konstanter av ovanstående flyttalstyp.
Lösning.

10.5 Egna datatyper

Fortran har tidigare inte tillåtet användardefinierade datatyper. Detta blir nu möjligt.
        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

Formen hos ett Fortran-program Innehåll Avancerad användning av subrutiner och funktioner


Senast modifierad: 3 maj 1999
boein@nsc.liu.se