Fortran est un langage de programmation pensé dans les années 50 pour le calcul scientifique. Il continue à être largement utilisé dans le domaine de la recherche scientifique pour le calcul intensif. Contrairement à des langages plus évolués, le déroulement des opérations individuelles en machine reste relativement contrôlable dans le langage, ce qui permet d'écrire des codes relativemet efficaces en termes de calcul. Le Fortran a beaucoup évolué ces dernières années avec l'apparition de la norme Fortran 90.
Les normes récentes (Fortran 95) sont beaucoup plus riches mais aussi beaucoup plus difficiles à appréhender que la norme Fortran 77. Nous présentons donc ici les bases fondamentales de Fortran en se basant principalement sur Fortran 77 mais en utilisant (implicitement ou explicitement) quelques avancées significatives provenant de la norme 90-95.
Ce cours est une introduction destinée à des applications simples sur des ordinateurs de type PC-Linux. Les exemples commençant par
program NON
sont disponibles sous la forme de fichiers NOM.f dans le répetroire
/home/lpasf/FORTRAN/EXEMPLES
sur la machine branly du réseau du LPAOSF. Tous les programmes ont été testés avec le compilateur gratuit développé pour Linux dans le cadre du projet GNU : g95.
Un programme Fortran est d'abord une suite d'instructions en caractères ASCII. Le fichier contenant ces instructions doit se terminer en .f, .F (ou .f90 ou .F90 si ont suit la norme Fortran 90 pour l'écriture des fichiers).
En fortran 77, les lignes se limitent à 80 caractères. Les 6 premiers caractères ont un statut particulier :
Un programme Fortran commence par une instruction "program" et se termine par une instruction "end".
On montre ici un petit exemple
La commande
print*,chaine_de_caracteres
imprime à l'écran une chaîne de caractère. Une chaîne de caractères peut être une suite de caractères entourés par des '. dans un fichier
test.fpar exemple, on doit compiler le programme , c'est à dire le transformer dans un langage directement compréhensible par le c
g95 test.f
qui va créer un executable a.out ou bien
g95 test.f -o test
qui renommera l'executable test au lieu de a.out. On lance un executable en tappant simplement
a.out
ou "test" dans la fenêtre shell.
En fortran 90, on peut écrire dés le premier caractère. Les commentaires commencent par un !. Les lignes peuvent dépasser les 80 caractères.
Pour l'essentiel, il existe une compatibilité amont entre les deux versions de Fortran, c'est à dire qu'un code écrit en fortran 77 peut être compilé en fortran 90/95. Ceci ne concerne pas cependant le format d'écriture décrit ci-dessus. Si on suit le format fortran 77, on nommera le fichier program.f. Si on suit le fortran 95, program.f90. Dans tous les cas, la compilation s'effectuera avec la même commande g95.
Le programme premiers_pas.f, devient, avec la norme d'écriture f90 le programme
premiers_pas.f90 ci-dessous
Une variable est un emplacement dans la mémoire vive (RAM) de l'ordinateur, dans laquelle on va pourvoir stoker des valeurs.
On distingue en Fortran essentiellement 4 types de variables : les caractères (character), les nombres réels (real) les nombres entiers (integer), les variables logiques vrai/faux (logical).
Une variable possède un nom (xxx, par exemple) et on peut attribuer une valeur à cette variable via l'instruction
xxx=1.
En théorie, en Fortran, les vraiables ont un type prédéfini en fonction de leur première lettre. Par exemple, une variable commençant par un 'x' est un réel alors qu'une variable commençant par un 'i' est un entier. Cette attribution automatique des types de variables est en fait un piège, et il DOIT ETRE OUBLIER CAR IL EST LA SOURCE D'ERREUR A L'INFINI. Concrêtement, on rajoute juste après la première ligne du programme une ligne
implicit none
qui annule cette attribution automatique des types.
Du coup, toute variable doit être déclarée avant d'être utilisée. Les déclarations doivent être placées immédiatemment après la commande 'implicit none'.
Exemple de déclaration et attribution de variables
La valeur d'un nombre real comporte en général un '.'.
Par exemple, on peut écrire le réel
:
0.00102, .001020 ou encore 1.02E-3.
L'instruction 'xxx=1' va également attribuer la valeur real 1. à la
variable xxx mais seuelement parce que xxx a été déclaré en real.
Il est donc plus propre d'écrire 'xxx=1.'.
La représentation en machine des nombres entier '1' et réel '1.' est complètement différente.
La base de la représentation en machine est toujours le codage en base 2, avec
des 0 et des 1.
Un entier sera directement décomposé en base deux, en gardant un bit pour le
signe.
Les réels sont représentés sous la forme
.
Les nombres
,
et le signe sont codés en base deux.
Suivant les compilateurs, les 'real' peuvent prendre plus ou moins
de place la mémoire vive de la machine.
Sur un PC-Linux standard, avec g95, les 'real' occupent par défaut
4 octets (bytes en anglais) soit 48=32 bits.
Les entiers sont codés pour leur part sur deux octets (16 bits).1
Une chaîne de caractère est une suite de caracères compris entre deux signes '. On peut concaténer deux chaînes de caractères (les mettre bout à bout) avec le signe // (par exemple 'premiere chaine '//'seconde chaine')
La commande
print*,A,B,C
imprime à l'écran successivement A, B et C, où A, B et C peuvent être des noms de vriables de n'importe quel type ou, directement les valeurs correspondantes.
Exercice :
quel sera le résultat de l'execution du programme
On peut également attribuer des valeurs aux variables par les instructions 'data' ou 'parameter', située après la déclaration de la variable mais avant la fin des déclarations.
On peut définir des tableaux contenant une suite de variables d'un même type. Cette déclaration se fait en rajoutant des parenthèses après la variable comme
real xtab(3,2)
les nombres 10 et 3 s'appellent les dimensions du tableau. Il est préférable de définir ces dimensions préalablement comme des variables (entières). Il faut alors attribuer des valeurs à ces variables via l'instruction parameter.2
Les éléments d'un tableau sont en fait rangés dans la mémoire de l'ordinateur
comme une suite linéaire de variables, organisée de la façon suivante :
xxx(i=1,j=1),xxx(i=2,j=1,) ... xxx(i=im,j=1), xxx(i=1,j=2), xxx(i=2,j=2) ...
Les boucles iteratives peuvent prendre différentes formes. La plus classique est la forme 'Do ... enddo'
Il existe aussi en fortran 95 des boucles conditionnelles "Do while"
Les caractères 'i10' correspondent en fait à une valeur logique qui
est vraie tant que 'i
10'. On revient sur la définition des variables
logiques ci-dessous.
Il existe aussi en fortran la possibilité d'effectuer des boucles implicites. Pour un tableau 'real xxx(im)', on peut par exemple récrire l'initialisation des valeurs du tableau à 0., à savoir
do i=1,im xxx(i)=0. enddo
plus simplement comme
xxx(1:im)=0.
ou encore, de façon plus synthétique
xxx(:)=0.
ou même
xxx=0.
La forme 'xxx(1:im)' est souvent préférable car elle rappelle à quelqu'un qui lit le programme, que 'xxx' est un tableau à une dimension, de dimension 'im'.
On montre ci-dessous un petit exemple qui calcule la dérivée en y d'un champ bi-dimensionnel utilisant des boucles plus ou moins implicites. Les 3 façons de faire sont identiques mais la plus synthétique n'est pas forcément la plus facile à lire.
D'autres formes de boucles implicites existent pour la lecture ou l'écriture. Par exemple, si on veut écrire à l'écran juste la première ligne du tableau 'xxx' du programme 'les_boucles', on pourra simplement écrire
print*,'premiere ligne du tableau xxx :',(xxx(i,1),i=1,im)
ou encore, en fortran 90
print*,'premiere ligne du tableau xxx :',xxx(:,1)
Les variables logiques prennent la valeur vrai (.true.) ou faux (.false.).4Les valeurs logiques peuvent être utilisées notamment pour des boucles 'do while' ou pour des tests 'if, else, endif'.
En pratique, les variables logiques sont souvent le résultat d'égalités
ou inégalités : 'i10', 'i==10'
ou de combinaison d'inégalités via des combinaisons avec des opérateurs
logiques '.and.', '.or.' ou '.not.'. On peut mettre des parenthèses
autour des conditions '(i
10).and.(i
10)'.
Dans la norme fortran 77, les égalités et
inégalités ne pouvaient pas s'écrire avec les signes ',
ou =='.
Math | ![]() |
![]() |
= | ![]() |
![]() |
Fortran 77 | .le. | .lt. | .eq. | .gt. | .ge. |
Fortran 95 | ![]() |
![]() |
== | ![]() |
![]() |
On montre ci-dessous un petit exemple d'utilisation de if, else, endif
On peut effectuer sur les variables entières ou réelles les opérations
classiques :
addition '+', multiplication '*', division '/'.
On peut inclure dans les opérations des parenthèses.
Les règles de composition suivent les conventions mathématiques classiques.
est l'équivalent de
et
correspond à
.
Fortran possède également tout un tas de fonctions prédéfinies dont l'argument est inséré dans une parenthèse comme la racine carrée (notée sqrt(x)) , les fonctions trigonométriques (sin(x), cos(x), tan(x), tanh(x) ...).
Les puissances comme se nottent
.
Les entrées et sorties sont des éléments essentiels d'un programme. Le minimum des sorties est un 'print*' qui donne, à l'écran le résultat d'un calcul.
Les commandes de base pour écrire (sortie) ou lire (entrée) des variables depuis ou vers l'extérieur sont respectivement les instructions write et read. Les écritures et lectures se font vers ou depuis soit des fichiers de données, soit les 'sorties et entrées standard', à savoir l'écran et le clavier. Dans le cas où on lit/écrit sur des fichiers, ces fichiers peuvent être soit des fichiers de caractères ASCII, soit direcment des représentations binaires des nombres (c'est à dire par exemple la suite des 32 bits utilisés pour coder un real).
La syntaxe de base est la même pour les deux. Par exemple pour écrire le contenu de la variable reelle xxx sous forme de caractères (par exemple 1.02000e-3) on écrira
write(unit,format) xxx
'unit' repère là où on va écrire ou lire et 'format' définit le format.
'unit' prendra la valeur '*' pour read (resp. write) si on veut lire (resp. écrire) sur l'entrée (resp. la sortie) standard, c'est à dire depuis le clavier (resp. sur l'écran). Si 'format' prend la valeur '*', le format est libre, ce qui veut dire que c'est Fortran qui choisit le format. Par exemple
integer i write(*,*) 'entrer une valeur de i' read(*,*) i write(*,*) 'vous avez entre i=',i
lit une valeur d'un entier entré au clavier. Remarquer que les instructions 'print*,' et 'write(*,*) ' sont équivalentes.
Si on ne veut pas utiliser le format standard, on doit spécifier un format. Ceci peut se faire de deux façons. Soit en définissant un format avec un numéro. Exceptionnellement, ce numéro sera écrit dans les premières colonnes du programme fortran
1000 format(3f10.2)
qui veut dire qu'on définit un format, étiqueté 1000, et qui consiste en l'écriture de 3 réels succsessifs, écrits chacun sur 10 caractères avec 2 caractères après la virgule.
Exemple :
Comme le tableau xxx contient en fait ici 6 élément et que le format n'écrit que 3 nombres réels, les 6 éléments seront écrit trois par trois sur deux lignes consécutives.
Pour savoir comment spécifier les formats, il faut se reporter à une documentation plus complète. Mais, en général, il est plus simple d'utiliser le format libre '*' qui permet de se débrouiller dans la plupart des cas.
Pour écrire ou lire dans un fichier, il faut d'abord ouvrir ce fichier en utilisant la commande open.
Si le fichier est un fichier ascii, l'ouverture se fait par
open(10,file='le_fichier_ascii',form='formatted')
Et on écrira ou lira alors dans ce fichier avec une instruction du genre 'read(10,*)' ou 'write(10,*)'. On peut aussi écrire 'file=chaine' ou chaine est déclaré comme une chaîne de caractères à laquelle on attibue une valeur.
Pour les fichiers binaires, il en existe (au moins) deux types. Les fichiers binaires standard et les fichiers à accès direct. Dans les premiers, on écrit les binaires les uns à la suite des autres. Pour relire ces fichiers, on est essentiellement obligé de tout relire depuis le début. Les seconds, appelés fichiers à accès direct, sont organisés sous la forme d'enregistrements (ou 'record') de tailles identiques.
Les ouvertures de ces fichiers s'écrivent
integer taille_d_un_record taille_d_un_record=4*100 open(10,file='fichier_binaire',form='unformatted') open(11,file='fichier_acces_direct',form='unformatted' s access='direct',recl=taille_d_un_record
Dans le second cas, on ouvre, avec l'étiquette 11, un fichier en accès direct composé d'enregistrements de 400 octets, soit par exemple de quoi stoker 100 nombres 'real*4'.
L'écriture dans un fichier binaire se fait sans format avec une instruction 'write(10) xxx' par exemple pour un fichier binaire simple et en spécifiant le numéro de l'enregistement pour le fichier à accès direct. 'wtite(11,rec=2) xxx' signifie qu'on écrit le tableau xxx sur l'enregistrement 2 du fichier 11.
On montre ci-dessous un exemple d'ecriture dans un fichier en acces direct et relecture avec changement de l'ordre de lecture.
On peut spécifier beaucoup d'autres options dans les commandes open. Par exemple ...,status='old',.. signifie que le fichier est ancien. Dans ce cas, si le fichier n'existe pas déjà, il ne sera pas ouvert. status peut prendre la valeur 'old', 'new' ou 'unknown'. La valeur 'new' est utile si on veut eviter d'écraser un fichier qui existe déjà (je programme refusera d'ouvrir le fichier dans ce cas).
Dés qu'il est un peu long, un programme doit être découpé en tâches indépendantes et qu'on puisse tester indépendamment.
On va se concentrer ici sur l'utilisation des sous-programmes ou 'subroutine'. Un sous-programme diffère d'un programme (dit principal) parce qu'il ne peut pas être éxecuter seul. Il ne sera éxecuté que si un programme principal (ou un sous-programme lui-même appelé par un programme) fait appel à lui via l'instruction call.
Les sous-programmes cooencent donc par une instruction
subroutine sous_programme(arguments,...) implicit none
(ne pas oublier de répéter la commande 'implicit none' dans tous les sous-programmes !) et se termine par
return end
Entre les deux, on trouve les mêmes déclarations et instructions que dans un programme principal. L'instruction 'return' signifie qu'on retourne au programme principal. Il peut éventuellement y en avoir plusieurs dans le même sous-programme, sous différentes options contrôlées par une instruction 'if' par exemple.
Le sous-programme peut disposer d'un certain nombre d'arguments. Les arguments sont des variables qui sont échangées entre le programme appelant et le sous-programme appelé. Le meme argument peut avoir un nom différent dans les deux programmes. Un exemple simple
Attention : le même argument doit avoir le même type dans les programmes appelant et appelé. Il peut être bon de tester le programme
Pour le passage de tableaux en argument, il existe différentes conventions. En Fortran 77 Standard, ce qui est échangé entre les programmes appelant et appelé, c'est l'adresse du premier élément du tableau. Comme les tableaux sont écrits de façon séquentielle dans la mémoire de l'ordinateur (occuppant des cases mémoires consécutives), on sait ce qui se trouve derrière le premier élément.
En Fortran 90, on peut aussi passer directement un tableau complet, en, et même des sous-parties d'un tableau en spécifiant la partie du tableau que l'on passe. Par exemple tab(4:6,2:10) correspond à la sous-matrice de la matrice tab(i,j) avec i variant de 4 à 6 et j variant de 2 à 10.
On montre ci-dessous des exemples d'utilisation de ces différents modes de passage d'arguments.
On vient de voir comment on passe des variables en arguments entre programme appelant et programme appelé.
Il existe aussi un moyen de réserver une zone mémoire pour des variables, qu'on pourra ensuite appeler à partir de n'importe quelle partie du programme. Il s'agit des "common block". Un common est composé d'un identifiant et d'une liste de variables. Par exemple :
Les common sont considérés comme obsolètes en fortran 95 mais sont encore beaucoup utilisés pour stoker des constantes.
Par oposition aux variables globales des 'common', on peut définir des variables locales, internes à un sous-programme, qui ne seront pas vues de l'extérieure.
Suivant la version de fortran, les options de compilation, etc ... les variables locales à un sous-programme peuvent être gérées de façon très différentes par l'ordinateur.
A priori, il faut partir du principe que d'un appel à l'autre, une variable locale a pu changer de valeur. C'est à dire que la zone mémoire attribuée à cette variable pendant l'execution du sous-programme a pu, entre deux appels au sous-programme, être utilisée, par exemple pour mettre une variable locale d'un autre sous-programme. C'est ce qu'on appelle l'allocation dynamique de mémoire.
Si on veut forcer à ce que la valeur d'une variable soit retenue
d'un appel sur l'autre, il faut inclure une instruction save
pour sauvegarder cette variable particulière.
On montre ci-dessous un exemple où on calcule un sinus à partir
d'un nombre en degrés. On a besoin du nombre pour transformer
les degrés en radian. Pour éviter de recalculer
à chaque fois,
on ne le calcule qu'au premier appel, identifié lui-même par une clef logique
sauvegardée, et on le sauvegarde pour les appels suivants.
Remarquer que l'instruction 'data' initialise la valeur de la variable
au départ mais pas à chaque appel.
Le fait que des variables puissent utiliser des mêmes zones mémoires dans des sous-programmes différents, s'appelle l'allocation dynamique de mémoire. Par opposition, on parlera d'allocation statique si chaque variable se voit attribuer, en début de programme, une zone mémoire bien définie qui lui sera reservée pendant toute l'exécution de programme.
L'allocation dynamique est beaucoup plus économique pour la mémoire.
L'allocation dynamique de mémoire est sans doute le gain principal du passage de la norme 77 à 90. Avec l'allocation statique, les dimensions d'un tableau doivent être définies une fois pour toute, soit en dur - tab(10,7) par exemple - soit avec des dimensions définies au préalables comme 'parameter' comme on l'a vu plus haut.
Avec l'allocation dynamique de mémoire, on peut définir, dans un sous programme, des tableaux dont la dimension n'était pas connue a priori. C'est le cas par exemple de l'exemple ci-dessous.
Les sous-programmes peuvent être écrits dans le même fichier que le programme principal. Ils peuvent aussi être écrits dans des fichiers indépendants : program.f, sub1.f, sub2.f ... Ceci permet d'utiliser le sous-programme sub1.f pour program.f mais aussi pour program2.f par exemple.
Il faut alors compiler d'un coup les différents fichiers
g95 program.f sub1.f sub2.f -o program program
cpp
make
make_make
This document was generated using the LaTeX2HTML translator Version 2002-2-1 (1.71)
Copyright © 1993, 1994, 1995, 1996,
Nikos Drakos,
Computer Based Learning Unit, University of Leeds.
Copyright © 1997, 1998, 1999,
Ross Moore,
Mathematics Department, Macquarie University, Sydney.
The command line arguments were:
latex2html -split 0 Fortran
The translation was initiated by Frédéric Hourdin on 2006-03-13