economia news e media viaggi informatica internet salute e benessere int rattenimento e spettacolo sport tempo libero istruzio ne e formazione arte cultura scienza

Il Sondaggio

Quale versione del fortran utilizzi?

Guarda i risultati

Manuali in italiano

Introduzione al linguaggio F

A cura di Giuseppe Ciaburro

Pubblicato il 08/06/2002

Una evoluzione del linguaggio Fortran 90

Il Fortran il cui nome deriva dal termine FORmula TRANslation è stato introdotto nel 1957 e rimane il linguaggio preferito da molti programmatori scientifici. Il nuovo fortran detto Fortran 90, è stato realizzato con una struttura simile al linguaggio C. Le novità essenziali del Fortran 90 sono: subroutines ricorsive, allocazione dinamica, puntatori, dati strutturati definiti dall’utente, moduli, e la notevole abilità di manipolare interi array.

Il Fortran 90 è totalmente compatibile con il Fortran 77 questo implica che sia ancora presente in esso della sintassi considerata ormai obsoleta. Il linguaggio F invece è una evoluzione del linguaggio Fortran 90 infatti include solo le caratteristiche moderne è compatto, e facile da imparare.

 

  • Introduzione

Di seguito si supporrà che il lettore abbia una certa familiarità con i concetti di base della programmazione, inizialmente forniamo un esempio di un programma F.


program prodotto
   real :: m, a, force
   m = 2.0                       ! massa in Kg
   a = 4.0                        ! accelerazione in unità mks 
   force = m*a                ! forza in Newton
print *, force
end program prodotto

Le novità del linguaggio F possono essere riassunte nei seguenti punti:

  • Il primo statement deve essere lo statement program; L’ultimo statement deve essere il corrispondente statement end program.
  • Le variabili numeriche di tipo intero e quelle floating point sono diverse. Il nome di tutte le variabili deve essere costituito da 1 a 31 caratteriri alfanumerici dei quali il primo deve essere una lettera e l’ultimo non deve essere un underscore.
  • Il tipo di ogni variabile deve essere dichiarato esplicitamente.
  • Il numero reale due deve essere scritto come 2.0 e non come 2.
  • Il linguaggio F è case sensitive ma due nomi che differiscono solo dal fatto di avere una o più lettere maiuscola o minuscola rispettivamente non possono essere utilizzate. Tutte le keywords (parole chiave del linguaggio che quindi non possono essere ridefinite) son scritte in minuscolo. Molti nome come ad esempio product sono riservati e non possono essere utilizzati come nomi.
  • I commentio iniziano con il simbolo ! e possono essere ovunque nel programma.
  • Gli statements su linee che possono contenere al più 132 caratteri.
  • Se si pone un asterisco (*) dopo il comando print si sceglie il formato di default.

Di seguito introduciamo la sintassi necessarie che ci permette di associare alle variabili m e a il valore da noi desiderato ed introdotto da tastiera. Da notare l’uso dello statement read non formattato.

program prodotto2
   real :: m, a, force
   ! SI units
   print *, "mass m = ?"
   read *, m
   print *, "acceleration a = ?"
   read *, a
   force = m*a
   print *, "force (in newtons) =", force
end program prodotto2

  • Il Costrutto Do

Il linguaggio F utilizza il costrutto do per fare in modo che il computer esegua gli stessi statements più di una volta. Un esempio di un costrutto do è di seguito riportato:

program serie
   integer :: n
   real :: sum_serie       ! sum è una keyword
   sum_serie = 0.0
!  aggiunge i primi 100 termini di una serie semplice
   do n = 1, 100
      sum_serie = sum_serie + 1.0/real(n)**2
      print *, n,sum_serie
   end do
end program serie

Da notare che n è una variabile integer. In questo caso lo statement do specifica il primo e l’ultimo valore di n; n si incrementa di una unità per defaultì. Il blocco degli statements interni al loop si indenta per chiarezza.

Poiché il prodotto n*n è ottenuto utilizzando un’aritmetica intera, iè meglio convertire n in variabile reale prima della moltiplicazione. Inoltre si può notare che l’esponente è ottenuto utilizzando l’operatore **.

 

  • Il costrutto IF

Nel prossimo esempio l’uscita dal loop Do è vincolata dal soddisfacimento di un test.

program serie_test
!  Illustrazione dell’uso del costrutto do 
   integer :: n
   real :: sum_series, newterm, relative_change
   n = 0
   sum_series = 0.0
   do
      n = n + 1
      newterm = 1.0/(n*n)
      sum_series = sum_series + newterm
      relative_change = newterm/sum_series
      if (relative_change < 0.0001) then
         exit
      end if
      print *, n,relative_change,sum_series
   end do
end program serie_test

Le novità introdotte dal linguaggio F presenti nell’esempio precedente sono:

  • L’uscita dal costrutto do è realizzata con l’utilizzo dello statement exit.
  • Il costrutto if determina l’esecuzione di una sequenza di statements (un blocco) che dipende da una condizione. Il costrutto if è un compound statement ed inizia con if ... then e termina con end if. Il blocco interno al costrutto if è indentato per chiarezza.

Tabella 1. Sommario degli operatori relazionali.

relazione

Operatore

Minore

<

Minore uguale

<=

Uguale

==

Non uguale

/=

Maggiore

>

Maqgiore uguale

>=


Il programma seguente illustra l’uso del parametro kind e del costrutto do con nome:

program series_example
!  illustra l’uso del parametro kind e del loop do con nome
   integer, parameter :: double = 8
   integer :: n
   real (kind = double) :: sum_series, newterm, relative_change
   n = 0
   sum_series = 0.0
   print_change: do
      n = n + 1
      newterm = 1.0/real(n, kind = double)**2
      sum_series = sum_series + newterm
      relative_change = newterm/sum_series
      if (relative_change < 0.0001) then
         exit print_change
      end if
      print *, n,relative_change,sum_series
   end do print_change
end program series_example

  • Le variabili possono avere una rappresentazione diversa a seconda dell’ hardware utilizzato, la rappresentazione in doppia precisione può essere ottenuta utilizzando kind = double in parentesi dopo la keyword real.

  • La rappresentazione in doppia precisione è ottenuta in Fortran 90 su macchine SGI e in F su macchine Macintosh ponendo:
double = 8

Inoltre la doppia precisione in F su macchine SGI è ottenuta ponendo:

 

double = 2

  • Gli statements do e end do devono avere lo stesso nome oppure devono non averlo entrambi. In generale, il costrutto do è nominato esplicitamente quando è inserito in un ciclo innestato. L’uso di un costrutto do con nome nell’esempio precedente è superfluo ed è stato inserito solo per illustrare tale tecnica.

I Subprograms sono chiamati dal main program o da altri subprograms. Ad esempio, nel programma seguente si aggiunge e si moltiplica due numeri introdotti da tastiera. Da notare che le variabili x e y sono public e sono disponibile dal main program.


module common
   public :: initial,add,multiply
   integer, parameter, public :: double = 8
   real (kind = double), public :: x,y

contains

subroutine initial()
   print *, "x = ?"
   read *,x
   print *, "y = ?"
   read *,y
end subroutine initial

subroutine add(sum2)
   real (kind = double), intent (in out) :: sum2
   sum2 = x + y
end subroutine add

subroutine multiply(product2)
   real (kind = double), intent (in out) :: product2
   product2 = x*y
end subroutine multiply

end module common

program tasks            ! illustra l’uso dei module e delle subroutines
! notare quante variabikli sono passate
   use common

   real (kind = double) :: sum2, product2
   call initial()                 ! initializza le variabili
   call add(sum2)           !  aggiunge due variabili
   call multiply(product2)
   print *, "sum =", sum2, "product =", product2
end program tasks

  • I Subprograms (subroutines e functions) sono contenuti nei moduli. La forma dei module, subroutine, e function è simile al main program.
  • Un module è richiamato nel main program con l’utilizzo dello statement use.
  • Le Subroutines sono chiamate nel main program utilizzando lo statement call.
  • Un subprogram è sempre richiamabile da altri segmenti presenti nel module.
  • I subprograms nei module sono preceduti dallo statement contains.
  • Variabili e subprograms possono essere dichiarati come public in un module e possono essere richiamabili dal main program (e da altri moduli).
  • Inoltre informazioni possono essere passate come argomenti ad ogni altro subprogram, esempi sono le variabili sum2 e product2 del programma precedente. Una parentesi tonda () è sempre necessaria se non ci sono argomenti. Ogni argomento locale del programma deve essere accompagnato dal suo intent.

  • intent in sta a significare che l’argomento dummy non può variare nel subprogram.
  • intent out sta a significare che l’argomento dummy non può essere usato fino a quando il suo valore non viene calcolato nel subprogram e passato al programma chiamante.
  • intent in out sta a significare che l’argomento dummy ha un suo valore iniziale, che cambia nel subprogram, ed il suo valore viene poi passato al programma chiamante.
  • I moduli possono essere contenuti in file separati dal programma principale.

 

  • Output formattato

 

Cerchiamo ora di anlizzare nel dettaglio quali direttive impartire per ottenere un output formattato. Abbiamo detto che l’asterisco * denota il formato di default, per ottenere invece un fortato specifico bisogna associare al comando di output una lista di descrittori di formato. Un esempio di direttiva di output formattato è la seguente:


print "(t7,a,t16,a,t28,a)", "tempo","T_caffè","T_caffè - T_stanza"

Il descrittore t (tab) è usato per saltare ad una specifica posizione di una linea di output. Il descrittore a (alfanumerico) è usato per le stringhe di caratteri. Un esempio di descrittore f (floating point) è dato da:

print "(f10.2,2f13.4)",t,T_caffè,T_caffè - T_stanza

Il descrittore f13.4 sta ad indicare che delle prossime 13 posizioni utilizzate per stampare un valore reale, 4 posti sono riservati per stampare le cifre decimali dopo la virgola. (Il punto decimale ed il segno meno occupano 2 posizioni delle 13 anzidette.) Il descrittore 2f13.4 sta ad indicare che il descrittore f13.4 è utilizzato 2 volte. Un altro descrittore è i (integer).


Un’altra novità nella sintassi del linguaggio F è nell’uso dello statement parameter:

real (kind = double), public, parameter :: g = 9.8

Un parameter è una costante con nome. Il suo valore è fissato atraverso la sua dichiarazione e non può essere modificato durante l’esecuzione del programma.

 

  • Files


Il programma seguente mostra come aprire un nuovo file, scriverci all’interno, chiudere lostesso ed infine come leggere dei dati da un file già esistente:

program save_data
!  illustra come scrivere e leggere su file
   integer :: i,j,x
   character(len = 32) :: file_name
   print *, "name of file?"
   read *, file_name
   open (unit=5,file=file_name,action="write",status="new")
   do i = 1,4
      x = i*i
      write (unit=5,fmt=*) i,x
   end do
   close(unit=5)
   open (unit=1,file=file_name,action="read",status="old")
   do i = 1,4
      read (unit=1,fmt = *) j,x
      print *, j,x
   end do
   close(unit=1)
end program save_data

Gli statements Input/output si riferiscono a particolari file attraverso l’impiego delle cosiddette unità (unit). Gli statements read e write non si riferiscono direttamente ad un file, ma si riferiscono ad un numero di un file che deve essere connesso al file in questione. Ci sono molti modi di utilizzare lo statement open, ma l’esempio precedente è tipico. Tra le parentesi tonde bisogna specificare l’unità alla quale associare il file, il nome del file, il valore dello specificatore action che può essere read, write, e readwrite (di default) ed infine status che può assumere i seguenti valori old, new, replace, oppure scratch.

Se si suppone di riutilizzare i dati ottenuti sullo stesso sistema su cui è stato compilato il programma si possono usare operazioni di input/output non formattate per salvare overhead, extra space, e roundoff error associati alla conversione dei valori tra rappresentazione interna ed esterna. Naturalmante l’uso di dati non formattati è strettamente dipendente dalla macchina e dal compilatore utilizzati. L’accesso non formattato è molto utile quando i dato sono generati da un programma e quindi analizzati da un altro programma residenti entrambi sulla stessa macchina. Per generare file non formattati basta omettere lo specificatore di formato.

 

 

  • Arrays


Le definizioni e l’uso degli array sono indicate nel programma seguente:

module common
   public :: initial,cross

   contains

   subroutine initial(a,b)
      real, dimension (:), intent(out) :: a,b
      a(1:3) = (/ 2.0, -3.0, -4.0 /)
      b(1:3) = (/ 6.0, 5.0, 1.0 /)
   end subroutine initial

   subroutine cross(r,s)
      real, dimension (:), intent(in) :: r,s
      real, dimension (3) :: cross_product
      ! note use of dummy variables
      integer :: component,i,j
      do component = 1,3
         i = modulo(component,3) + 1
         j = modulo(i,3) + 1
         cross_product(component) = r(i)*s(j) - s(i)*r(j)
      end do
      print *, ""
      print *, "three components of the vector product:"
      print "(a,t10,a,t16,a)", "x","y","z"
      print *, cross_product
   end subroutine cross

   end module common

   program vector            ! illustra l’uso degli array
      use common
      real, dimension (3) :: a,b
      real :: dot
      call initial(a,b)
      dot = dot_product(a,b)
      print *, "dot product = ", dot
      call cross(a,b)
   end program vector

Le novità nell’uso degli arrays sono:

  • Un array è dichiarato nella sezione dichiarativa del programma, del module, oppure delle procedure utilizzando l’attributo dimension. Degli esempi sono:

 

real, dimension (10) :: x,y
real, dimension (1:10) :: x,y
integer, dimension (-10:10) :: prob
integer, dimension (10,10) :: spin
  • Il numero degli elementi di un array è indicato tra parentesi, in particolare il minimo valore di default è 1. Per questa ragione i primi due statement precedenti sono equivalenti.
    Il minimo valore può essere un numero negativo.
  • Nell’esempio che segue è proposta la dichiarazione di un array bidimensionale.
  • Per assegnare ad ogni elemento dell’array un valore esplicito si può usare un costruttore di array che è costituito da una lista unidimensionale di valori separati da una virgola e delimetati da "(/" e "/)". Un esempio è:
  a(1:3) = (/ 2.0, -3.0, -4.0 /)

che è equivalente alle tre istruzioni seguenti:

  a(1) = 2.0
  a(2) = -3.0
  a(3) = -4.0
  • Da notare che per stampare un array basta digitare la seguente riga:


  print *, cross_product

Il linguaggio F ha molte funzioni per la moltiplicazione di array e matrici. Per esempio, la funzione dot_function che opera con due vettori e fornisce il prodotto scalare. Alcune funzioni per la manipolazione di array sono: maxval, minval, product, e sum.

 

  • Allocate statement


Una delle più importanti novità introdotte con il Fortran 90 è l’allocazione dinamica, attraverso la quale la grandezza degli array può essere modificata durante l’esecuzione del programma. L’uso degli statement allocate e deallocate è di seguito illustrata. E’ da notare l’uso del ciclo implied do.


program dynamic_array
!  semplice esempio di array dinamico
   real, dimension (:), allocatable :: x
   integer :: i,N
   
   N = 2
   allocate(x(N:2*N))
!  ciclo implied do
   x(N:2*N) = (/ (i*i, i = N, 2*N) /)
   print *, x
   deallocate(x)
   allocate(x(N:3*N))
   x = (/ (i*i, i = N, 3*N) /)
   print *, x
end program dynamic_array

Un esempio del modo in cui è possibile passare gli array è il seguente:

module param

integer, public, parameter :: double = 8

end module param

module common

use param
private

public :: initial

integer, public :: N

contains

subroutine initial(x)
   real (kind = double), intent(inout), dimension(:) :: x
   N = 100
   x(1) = 1.0
end subroutine initial

end module common

program test
   use param
   use common
   real (kind = double), allocatable,dimension (:) :: x
   N = 10
   allocate(x(N))
   call initial(x)
end program test

 

  • Random number sequences


Il Fortran 90 include diverse procedure di costruzione che si rivelano molto utili. Una delle più utili è la subroutine random_number. Sebbene sia una buona idea scrivere una routine per la generazione casuale di numeri utilizzando un algoritmo già testato su un particolare problema di interesse, è d’altra parte conveniente utilizzare la subroutine random_number nella fase di debugging o nel caso l’accuratezza dei dati non sia così importante. Il programma che segue illustra diversi usi della subroutine random_number e random_seed. E’ da notare che gli argomenti rnd della random_number devono necessariamente essere real, con intent out, e possono essere scalari oppure degli array.

program random_example
   real :: rnd
   real, dimension (:), allocatable :: x
   integer, dimension(2) :: seed, seed_old
   integer :: L,i,n_min,n_max,ran_int,sizer
!  genera interi random tra n_min e n_max
!  la dimensione di seed è 1 in F e 2 in Fortran 90.
   call random_seed(sizer)
   print *, sizer
!  illustra l’uso di put e get
   seed(1) = 1239
   seed(2) = 9863        ! necessaria in Fortran 90
   call random_seed(put=seed)
   call random_seed(get=seed_old)
!  conferma del valore di seed
   print *, "seed = ", seed_old
   L = 100         ! length of sequence
   n_min = 1
   n_max = 10
   do i = 1,L
      call random_number(rnd)
      ran_int = (n_max - n_min + 1)*rnd + n_min
      print *,ran_int
   end do
!  assegna i numeri random all’array x 
   allocate(x(L))
   call random_number(x)
   print "(4f13.6)", x
   call random_seed(get=seed_old)
   print *, "seed = ", seed_old
end program random_example

E’ da notare come la subroutine random_seed sia utilizzata per specificare seed. La specificazione è utile quando la stessa sequenza di numeri random è utilizzata per testare il programma.

 

 

  • Ricorsività


Un semplice esempio di definizione ricorsiva è fornito dalla funzione fattoriale:

factorial(n) = n! = n(n-1)(n-2) ... 1

La definizione ricorsiva del fattoriale è:

factorial(1) = 1 factorial(n) = n factorial(n-1)

Vediamo come è possibile introdurre tale definizione in un programma:

module fact

public :: f
contains

recursive function f(n) result (factorial_result)
   integer, intent (in) :: n
   integer :: factorial_result

   if (n <= 0) then
      factorial_result = 1
   else
      factorial_result = n*f(n-1)
   end if
end function f

end module fact

program test_factorial
   use fact

   integer :: n
   print *, "integer n?"
   read *, n
   print "(i4, a, i10)", n, "! = ", f(n)
end program test_factorial

Nel programma greatest che segue, (ricavato dal The Fun of Computing,John G. Kemeny, True BASIC (1990)) dati due interi, n e m, si ricava il massimo comun divisore. Ad esempio, se n = 1000 e m = 32, allora il massimo comun divisore (gcd) è gcd = 8.

Un metodo per la ricerca del gcd è quello di dividere n per m. Si scrive n = q m + r, dove q è il quoziente e r è il resto. Se r = 0, allora m divide n e m è il gcd. Altrimenti, un qualsiasi divisore tra m ed r divide anche n, allora risulta gcd(n,m) = gcd(m,r). Poichè r < m, abbiamo reso la ricerca più facile. Ad esempio, sia n = 1024 e m = 24. Allora q = 42 e r = 16. La nostra ricerca si riduce dunque a gcd(24,16). Essendo q = 1 e r = 8 si calcola il gcd(16,8). In definitiva q = 2, e r = 0 cosicchè gcd = 8. L’esempio seguente implementa tale algoritmo.

module gcd_def

public :: gcd
contains

recursive function gcd(n,m) result (gcd_result)
   integer, intent (in) :: n,m
   integer :: gcd_result
   integer :: remainder

   remainder = modulo(n,m)
   if (remainder == 0) then
      gcd_result = m
   else
      gcd_result = gcd(m,remainder)
   end if
end function gcd

end module gcd_def

program greatest
   use gcd_def

   integer :: n,m
   print *, "enter two integers n, m"
   read *, n,m
   print "(a,i6,a,i6,a ,i6)", "gcd of",n," and",m,"=",gcd(n,m)
end program greatest

Un ulteriore esempio di ricorsività è fornito dalla cosiddetta torre di Hanoi.

Il volume di una ipersfera d-dimensionale per unità di raggio può essere relazionato all’area di una ipersfera (d - 1)-dimensionale. Il programma seguente utilizza una subroutine ricorsiva per sfruttare tale proprietà e calcolare quindi il volume per unità di area di una ipersfera d-dimensionale:

module common
   public :: initialize,integrate

   integer, parameter, public :: double = 8
   real (kind = double), parameter, public :: zero = 0.0
   real (kind = double), public :: h, volume
   integer, public :: d

contains

subroutine initialize()
   print *, "dimension d?"
   read *, d                         ! spatial dimension
   print *, "integration interval h?"
   read *, h
   volume = 0.0
end subroutine initialize

recursive subroutine integrate(lower_r2, remaining_d)
!  lower_r2 is contribution to r^2 from lower dimensions
   real(kind = double),intent (in) :: lower_r2
   integer, intent (in) :: remaining_d  ! # dimensions to integrate
   real (kind = double) :: x

   x = 0.5*h   ! mid-point approximation
   if (remaining_d > 1) then
      lower_d: do
         call integrate(lower_r2 + x**2, remaining_d - 1)
         x = x + h
         if (x > 1) then
            exit lower_d
         end if
      end do lower_d
   else   
      last_d: do
         if (x**2 + lower_r2 <= 1) then
           volume = volume + h**(d - 1)*(1 - lower_r2 - x**2)**0.5
         end if
         x = x + h
         if (x > 1) then
            exit last_d
         end if
      end do last_d
   end if
end subroutine integrate

end module common

program hypersphere
!  programma originale di Jon Goldstein
   use common

   call initialize()
   call integrate(zero, d - 1)
   volume = (2**d)*volume         
   print *, volume
end program hypersphere

 

 

  • Variabili di tipo carattere


L’unico operatiore intinseco per le espressioni contenenti caratteri è l’operatore di concatenazione //. Per esempio, la concatenazione tra le costanti di carattere string e beans si scrive come:

"string"//"beans"

Il risultato, stringbeans, può essere assegnato ad una variabile di tipo carattere. Un esempio di concatenazione utile è dato dal seguente programma.

program write_files
!  Programma test per l’apertura di un file e la scrittura di dati
   integer :: i,n
   character(len = 15) :: file_name
   n = 11
   do i = 1,n
!     assegna number.dat a file_name utilizzando lo statement write 
      write(unit=file_name,fmt="(i2.2,a)") i,".dat" 
!     // è l’operatore di concatenazione
      file_name = "config"//file_name
      open (unit=1,file=file_name,action="write",status="replace")
      write (unit=1, fmt=*) i*i,file_name
      close(unit=1)
   end do
end program write_files

In tale programma è da notare l’uso dello statement write per costruire una stringa di caratteri costituita da numeri e caratteri.

 

  • Variabili Complesse


Il programma che segue mostra come il Fortran 90 definisce ed utilizza le variabili complesse.

program complex_example
   integer, parameter :: double = 2
   real (kind = double), parameter :: pi = 3.141592654
   complex (kind = double) :: b,bstar,f,arg
   real (kind = double) :: c
   complex :: a
   integer :: d
   ! Una costante complessa è scritta come due numeri reali separati
   ! da una virgola e racchiusi in parentesi.
   a = (2,-3)
   ! Se una parte ha un kind, l’altra parte deve avere lo stesso kind
   b = (0.5_double,0.8_double)
   print *, "a =", a    ! notare che a ha una minore precisione rispetto b
   print *, "a*a =", a*a
   print *, "b =", b
   print *, "a*b =", a*b
   c = real(b)     ! real part of b
   print *, "real part of b =", c
   c = aimag(b)        ! parte immaginaria di b
   print *, "imaginary part of b =", c
   d = int(a)
   print *, "real part of a (converted to integer) =", d
   arg = cmplx(0.0,pi)
   b = exp(arg)        ! disposto su due linee per una più agevole lettura
   bstar = conjg(b)  ! complesso coniugato di b
   f = abs(b)             ! valore assoluto di b
   print *, "properties of b =", b,bstar,b*bstar,f
end program complex_example

 

 

  • Bibliografia
  • Walter S. Brainerd, Charles H. Goldberg, and Jeanne C. Adams, Programmer's Guide to F, Unicomp (1996).
  • Michael Metcalf and John Reid, The F Programming Language, Oxford University Press (1996).

Vuoi essere aggiornato sulle novità della guida?

Feed RSS XML vostro feed RSS