Cicli Iterativi in Fortran

Concetti Chiave
  • I Cicli Iterativi sono cicli che si ripetono un numero specificato di volte.
  • In Fortran, i cicli iterativi sono implementati usando il costrutto DO.
  • La sintassi di un ciclo iterativo DO include una variabile indice, un valore iniziale, un valore finale e un incremento opzionale.
  • La variabile indice viene aggiornata ad ogni iterazione del ciclo, e il ciclo continua finché la condizione di terminazione non è soddisfatta.
  • È possibile contare in discesa usando un incremento negativo nel ciclo DO.

Cicli Iterativi

Nel linguaggio Fortran, un ciclo che esegue un blocco di istruzioni un numero specificato di volte è chiamato ciclo DO iterativo o ciclo di conteggio.

Consiglio

I Cicli Iterativi sono conosciuti come Cicli FOR in altri linguaggi di programmazione

In altri linguaggi di programmazione, i cicli iterativi sono spesso chiamati cicli FOR. Per esempio, in C, C++, Java e JavaScript, i cicli iterativi sono chiamati cicli FOR. In Python, i cicli iterativi sono implementati usando la parola chiave for. Anche se il nome è diverso, il concetto è lo stesso.

Il costrutto del ciclo di conteggio ha la forma che segue:

DO index = istart, iend, incr
    ! Corpo del ciclo
    Istruzione 1
    ...
    Istruzione n
END DO

dove index è una variabile intera utilizzata come contatore del ciclo (nota anche come indice del ciclo). Le quantità intere istart, iend e incr sono i parametri del ciclo di conteggio; controllano i valori che la variabile index assume durante l'esecuzione. Il parametro incr è opzionale; se manca, si assume che sia 1.

Le istruzioni tra l'istruzione DO e l'istruzione END DO sono note come il corpo del ciclo. Vengono eseguite ripetutamente durante ogni passaggio del ciclo DO.

Il costrutto del ciclo di conteggio funziona come segue:

  1. Ognuno dei tre parametri del ciclo DO istart, iend e incr può essere una costante, una variabile o un'espressione. Se sono variabili o espressioni, i loro valori vengono calcolati prima dell'inizio del ciclo, e i valori risultanti vengono utilizzati per controllare il ciclo.
  2. All'inizio dell'esecuzione del ciclo DO, il programma assegna il valore istart alla variabile di controllo index.

    Se è vera la condizione:

    \texttt{index} \cdot \texttt{incr} \leq \texttt{iend} \cdot \texttt{incr}

    il programma esegue le istruzioni all'interno del corpo del ciclo.

  3. Dopo che le istruzioni nel corpo del ciclo sono state eseguite, la variabile di controllo viene ricalcolata come:

    \texttt{index} = \texttt{index} + \texttt{incr}

    Se \texttt{index}\cdot\texttt{incr} è ancora minore o uguale di \texttt{iend}\cdot\texttt{incr}, il programma esegue nuovamente le istruzioni all'interno del corpo.

  4. Il passo 2 viene ripetuto continuamente finché \texttt{index}\cdot\texttt{incr} \leq \texttt{iend}\cdot\texttt{incr}. Quando questa condizione non è più vera, l'esecuzione salta alla prima istruzione che segue la fine del ciclo DO.

Più semplicemente, bisogna interpretare le tre quantità istart, iend e incr come segue:

  • istart è il valore iniziale della variabile indice index.
  • iend è il valore finale della variabile indice index.
  • incr è l'incremento della variabile indice index ad ogni iterazione. Se assente è pari a 1.

Il numero di iterazioni che il ciclo DO effettua può essere calcolato utilizzando la seguente equazione

\texttt{iter} = \frac{\texttt{iend} - \texttt{istart} + \texttt{incr}}{\texttt{incr}}

Esaminiamo alcuni esempi specifici per rendere più chiaro il funzionamento del ciclo di conteggio. Primo, consideriamo il seguente esempio:

DO i = 1, 10
    Istruzione 1
    ...
    Istruzione n
END DO

In questo caso, le istruzioni da 1 a n verranno eseguite 10 volte. La variabile indice i sarà 1 la prima volta, 2 la seconda volta, e così via. La variabile indice sarà 10 nell'ultimo passaggio attraverso le istruzioni. Quando il controllo viene restituito all'istruzione DO dopo il decimo passaggio, la variabile indice i verrà aumentata a 11. Poiché 11 \cdot 1 > 10 \cdot 1, il controllo passerà alla prima istruzione dopo l'istruzione END DO.

Secondo, consideriamo il seguente esempio:

DO i = 1, 10, 2 
    Istruzione 1
    ...
    Istruzione n
END DO

In questo caso, le istruzioni da 1 a n verranno eseguite cinque volte. La variabile indice i sarà 1 la prima volta, 3 la seconda volta, e così via. La variabile indice sarà 9 nel quinto e ultimo passaggio attraverso le istruzioni. Quando il controllo viene restituito all'istruzione DO dopo il quinto passaggio, la variabile indice i verrà aumentata a 11. Poiché 11 \cdot 2 > 10 \cdot 2, il controllo passerà alla prima istruzione dopo l'istruzione END DO.

Terzo, consideriamo il seguente esempio:

DO i = 1, 10, -1
    Istruzione 1
    ...
    Istruzione n
END DO

Qui, le istruzioni da 1 a n non verranno mai eseguite, poiché vale che \texttt{index} \cdot \texttt{incr} > \texttt{iend} \cdot \texttt{incr} proprio la prima volta che si raggiunge l'istruzione DO. Invece, il controllo passerà alla prima istruzione dopo l'istruzione END DO.

Infine, consideriamo l'esempio:

DO i = 3, -3, -2
    Istruzione 1
    ...
    Istruzione n
END DO

In questo caso, le istruzioni da 1 a n verranno eseguite quattro volte. La variabile indice i sarà 3 la prima volta, 1 la seconda volta, −1 la terza volta e −3 la quarta volta. Quando il controllo viene restituito all'istruzione DO dopo il quarto passaggio, la variabile indice i verrà diminuita a −5. Poiché −5 \cdot −2 > −3 \cdot −2, il controllo passerà alla prima istruzione dopo l'istruzione END DO.

Possiamo rappresentare il flusso di controllo di un ciclo di conteggio con il diagramma di flusso mostrato nella figura che segue:

Diagramma di flusso del ciclo iterativo
Figura 1: Diagramma di flusso del ciclo iterativo

Esempio: Calcolo del Fattoriale

Per illustrare il funzionamento di un ciclo di conteggio, utilizzeremo un ciclo DO per calcolare la funzione fattoriale. La funzione fattoriale è definita come

n! = \begin{cases} 1 & n = 0 \\ n \cdot (n - 1) \cdot (n - 2) \cdot \dots \cdot 2 \cdot 1 & n > 0 \end{cases}

Il codice Fortran per calcolare N fattoriale per un valore positivo di N sarebbe

n_factorial = 1
DO i = 1, n
    n_factorial = n_factorial * i
END DO

Supponiamo di voler calcolare il valore di 5!. Se n è 5, i parametri del ciclo DO saranno istart = 1, iend = 5 e incr = 1. Questo ciclo verrà eseguito cinque volte, con la variabile i che assume i valori 1, 2, 3, 4 e 5 nei cicli successivi. Il valore risultante di n_factorial sarà 1 \cdot 2 \cdot 3 \cdot 4 \cdot 5 = 120.

Esempio: Calcolo del Giorno dell'Anno

Il giorno dell'anno è il numero di giorni (incluso il giorno corrente) trascorsi dall'inizio di un dato anno. È un numero nell'intervallo da 1 a 365 per gli anni ordinari, e da 1 a 366 per gli anni bisestili.

Proviamo a scrivere un programma Fortran che accetta un giorno, mese e anno, e calcola il giorno dell'anno corrispondente a quella data.

Una possibile implementazione è la seguente:

PROGRAM giorno_anno
! Scopo:
! Questo programma calcola il giorno dell'anno corrispondente a una
! data specificata. Illustra l'uso dei cicli di conteggio
! e del costrutto SELECT CASE.

IMPLICIT NONE

! Dizionario dati: dichiara tipi di variabili, definizioni e unità
INTEGER :: day         ! Giorno (gg)
INTEGER :: day_of_year ! Giorno dell'anno
INTEGER :: i           ! Variabile indice
INTEGER :: leap_day    ! Giorno extra per anno bisestile
INTEGER :: month       ! Mese (mm)
INTEGER :: year        ! Anno (aaaa)

! Ottieni giorno, mese e anno da convertire
WRITE (*,*) 'Questo programma calcola il giorno dell''anno data la '
WRITE (*,*) 'data corrente. Inserisci giorno(1-31), mese(1-12) '
WRITE (*,*) 'e anno in questo ordine: '
READ (*,*)  day, month, year

! Controlla per anno bisestile, e aggiungi giorno extra se necessario
IF ( MOD(year,400) == 0 ) THEN
    ! Secoli divisibili per 400 sono bisestili
    leap_day = 1
ELSE IF ( MOD(year,100) == 0 ) THEN
    ! Altri secoli non sono bisestili
    leap_day = 0
ELSE IF ( MOD(year,4) == 0 ) THEN
    ! Altrimenti ogni quarto anno è bisestile
    leap_day = 1
ELSE
    ! Tutti gli altri anni non sono bisestili
    leap_day = 0
END IF

! Calcola giorno dell'anno
day_of_year = day
DO i = 1, month-1
    ! Aggiungi giorni nei mesi da gennaio al mese scorso
    SELECT CASE (i)
    CASE (1,3,5,7,8,10,12)
        day_of_year = day_of_year + 31
    CASE (4,6,9,11)
        day_of_year = day_of_year + 30
    CASE (2)
        day_of_year = day_of_year + 28 + leap_day
    END SELECT
END DO

! Stampa i risultati
WRITE (*,*) 'Giorno            = ', day
WRITE (*,*) 'Mese              = ', month
WRITE (*,*) 'Anno              = ', year
WRITE (*,*) 'Giorno dell''anno  = ', day_of_year

END PROGRAM giorno_anno

Per determinare il giorno dell'anno, questo programma dovrà sommare il numero di giorni in ogni mese precedente al mese corrente, più il numero di giorni trascorsi nel mese corrente. Un ciclo DO verrà utilizzato per eseguire questa somma. Poiché il numero di giorni in ogni mese varia, è necessario determinare il numero corretto di giorni da aggiungere per ogni mese. Un costrutto SELECT CASE verrà utilizzato per determinare il numero appropriato di giorni da aggiungere per ogni mese.

Durante un anno bisestile, un giorno extra deve essere aggiunto al giorno dell'anno per qualsiasi mese dopo febbraio. Questo giorno extra tiene conto della presenza del 29 febbraio nell'anno bisestile. Pertanto, per eseguire correttamente il calcolo del giorno dell'anno, dobbiamo determinare quali anni sono bisestili. Nel calendario gregoriano, gli anni bisestili sono determinati dalle seguenti regole:

  1. Gli anni divisibili per 400 sono anni bisestili.
  2. Gli anni divisibili per 100 ma non per 400 non sono anni bisestili.
  3. Tutti gli anni divisibili per 4 ma non per 100 sono anni bisestili.
  4. Tutti gli altri anni non sono anni bisestili.

Abbiamo utilizzato la funzione MOD (per modulo) per determinare se un anno è divisibile per un dato numero. Se il risultato della funzione MOD è zero, allora l'anno è divisibile.

Si noti che il programma somma il numero di giorni in ogni mese prima del mese corrente, e che utilizza un costrutto SELECT CASE per determinare il numero di giorni in ogni mese.

Adesso proviamo il programma.

Utilizzeremo i seguenti risultati noti per testare il programma:

  1. L'anno 1999 non è un anno bisestile. Il 1° gennaio deve essere il giorno 1 dell'anno, e il 31 dicembre deve essere il giorno 365 dell'anno.
  2. L'anno 2000 è un anno bisestile. Il 1° gennaio deve essere il giorno 1 dell'anno, e il 31 dicembre deve essere il giorno 366 dell'anno.
  3. L'anno 2001 non è un anno bisestile. Il 1° marzo deve essere il giorno 60 dell'anno, poiché gennaio ha 31 giorni, febbraio ha 28 giorni, e questo è il primo giorno di marzo.

Se questo programma viene compilato e poi eseguito cinque volte con le date sopra indicate, i risultati sono

  • Input: 1 1 1999; Output: 1

     Questo programma calcola il giorno dell'anno data la 
     data corrente. Inserisci giorno(1-31), mese(1-12) 
     e anno in questo ordine: 
     1 1 1999
     Giorno            =            1
     Mese              =            1
     Anno              =         1999
     Giorno dell'anno  =            1
    
  • Input: 31 12 1999; Output: 365

     Questo programma calcola il giorno dell'anno data la 
     data corrente. Inserisci giorno(1-31), mese(1-12) 
     e anno in questo ordine: 
     31 12 1999
     Giorno            =           31
     Mese              =           12
     Anno              =         1999
     Giorno dell'anno  =          365
    
  • Input: 1 1 2000; Output: 1

     Questo programma calcola il giorno dell'anno data la 
     data corrente. Inserisci giorno(1-31), mese(1-12) 
     e anno in questo ordine: 
     1 1 2000
     Giorno            =            1
     Mese              =            1
     Anno              =         2000
     Giorno dell'anno  =            1
    
  • Input: 31 12 2000; Output: 366

     Questo programma calcola il giorno dell'anno data la 
     data corrente. Inserisci giorno(1-31), mese(1-12) 
     e anno in questo ordine: 
     31 12 2000
     Giorno            =           31
     Mese              =           12
     Anno              =         2000
     Giorno dell'anno  =          366
    
  • Input: 1 3 2001; Output: 60

     Questo programma calcola il giorno dell'anno data la 
     data corrente. Inserisci giorno(1-31), mese(1-12) 
     e anno in questo ordine: 
     1 3 2001
     Giorno            =            1
     Mese              =            3
     Anno              =         2001
     Giorno dell'anno  =           60
    

Il programma fornisce le risposte corrette per le nostre date di test in tutti e cinque i casi di test.

Esempio: Analisi Statistica con Valori Positivi e Negativi

Implementiamo un algoritmo che legge un insieme di misurazioni e calcola la media e la deviazione standard del set di dati di input, quando qualsiasi valore nel set di dati può essere positivo, negativo o zero.

Per scrivere questo programma, chiederemo all'utente il numero di valori di input, e poi utilizzeremo un ciclo DO per leggere quei valori.

L'implementazione è la seguente:

PROGRAM statistiche_valori
!
! Scopo:
! Per calcolare la media e la deviazione standard di un set di dati
! di input, dove ogni valore di input può essere positivo, negativo,
! o zero.
!
IMPLICIT NONE

! Dizionario dati: dichiara tipi di variabili, definizioni e unità
INTEGER :: i        ! Indice del ciclo
INTEGER :: n = 0    ! Il numero di campioni di input.
REAL :: std_dev     ! La deviazione standard dei campioni di input.
REAL :: sum_x = 0.  ! La somma dei valori di input.
REAL :: sum_x2 = 0. ! La somma dei quadrati dei valori di input.
REAL :: x = 0.      ! Un valore di dati di input.
REAL :: x_bar       ! La media dei campioni di input.

! Richiede il numero di punti da inserire.
WRITE (*,*) 'Inserisci numero di punti: '
READ (*,*) n

! Controlla se abbiamo abbastanza dati di input.
IF ( n < 2 ) THEN
    ! Dati insufficienti
    WRITE (*,*) 'Almeno 2 valori devono essere inseriti.'
ELSE
    ! Abbiamo abbastanza dati.

    ! Ciclo per leggere valori di input.
    DO i = 1, n
        ! Leggi valori
        WRITE (*,*) 'Inserisci numero: '
        READ (*,*) x
        WRITE (*,*) 'Il numero inserito è ', x
        ! Accumula somme.
        sum_x = sum_x + x
        sum_x2 = sum_x2 + x**2
    END DO

    ! Ora calcola statistiche.
    x_bar = sum_x / real(n)
    std_dev = sqrt((real(n)*sum_x2 - sum_x**2) / (real(n)*real(n-1)))

    ! Stampa il risultato.
    WRITE (*,*) 'La media di questo set di dati è:', x_bar
    WRITE (*,*) 'La deviazione standard è: ', std_dev
    WRITE (*,*) 'Il numero di punti dati è:', n
END IF

END PROGRAM statistiche_valori

Osservazioni sui Cicli DO

Ora che abbiamo visto esempi di un ciclo DO di conteggio in funzione, esamineremo alcuni dettagli importanti necessari per utilizzare correttamente i cicli DO.

  1. Non è necessario indentare il corpo del ciclo DO come abbiamo mostrato sopra. Il compilatore Fortran riconoscerà il ciclo anche se ogni istruzione in esso inizia nella colonna 1. Tuttavia, il codice è molto più leggibile se il corpo del ciclo DO è indentato, quindi si dovrebbe sempre indentare i corpi dei cicli DO.

    Consiglio

    Conviene sempre indentare il corpo di un ciclo DO

    Il consiglio è quello di indentare sempre il corpo di un ciclo DO di due o più spazi per migliorare la leggibilità del codice.

  2. La variabile indice di un ciclo DO non deve essere modificata da nessuna parte all'interno del ciclo DO. Poiché la variabile indice è utilizzata per controllare le ripetizioni nel ciclo DO, modificarla potrebbe produrre risultati inaspettati. Nel caso peggiore, modificare la variabile indice potrebbe produrre un ciclo infinito che non si completa mai. Consideriamo il seguente esempio:

    PROGRAM errore
    INTEGER :: i
    DO i = 1, 4
        i = 2
    END DO
    END PROGRAM errore
    

    Se i viene reimpostato a 2 ogni volta attraverso il ciclo, il ciclo non finirà mai, perché la variabile indice non potrà mai essere maggiore di 4! Questo ciclo continuerà all'infinito a meno che il programma che lo contiene non venga terminato. Quasi tutti i compilatori Fortran riconosceranno questo problema, e genereranno un errore a tempo di compilazione se un programma tenta di modificare una variabile indice all'interno di un ciclo.

    Nota

    Attenzione: Cicli Infiniti

    Non modificare mai il valore di una variabile indice di ciclo DO mentre si è all'interno del ciclo. Questo può causare un ciclo infinito che non si completa mai.

  3. Se il numero di iterazioni calcolato usando l'equazione vista sopra è minore o uguale a zero, le istruzioni all'interno del ciclo DO non vengono mai eseguite. Per esempio, le istruzioni nel seguente ciclo DO non verranno mai eseguite

    DO i = 3, 2
        ...
    END DO
    

    poiché

    \texttt{iter} = \frac{\texttt{iend} - \texttt{istart} + \texttt{incr}}{\texttt{incr}} = \frac{2 - 3 + 1}{1} = 0
  4. È possibile progettare cicli DO di conteggio che contano in discesa oltre che in salita. Il seguente ciclo DO esegue tre volte con i che è 3, 2 e 1 nei cicli successivi.

    DO i = 3, 1, -1
        ...
    END DO
    
  5. La variabile indice e i parametri di controllo di un ciclo DO dovrebbero sempre essere di tipo integer.

    L'uso di variabili reali come indici di cicli DO e parametri di controllo di cicli DO era una caratteristica legale ma indesiderabile di Fortran. È stata dichiarata obsoleta in Fortran 90, ed è stata completamente eliminata da Fortran 95.

  6. È possibile uscire da un ciclo DO in qualsiasi momento mentre il ciclo è in esecuzione. Se l'esecuzione del programma esce da un ciclo DO prima che altrimenti finisca, la variabile indice del ciclo mantiene il valore che aveva quando si verifica il salto. Si consideri il seguente esempio:

    INTEGER :: i
    DO i = 1, 5
        ...
        IF (i >= 3) EXIT
        ...
    END DO
    WRITE (*,*) i
    

    L'esecuzione uscirà dal ciclo DO e andrà all'istruzione WRITE al terzo passaggio attraverso il ciclo. Quando l'esecuzione arriva all'istruzione WRITE, la variabile i conterrà un valore di 3.

  7. Se un ciclo DO si completa normalmente, il valore della variabile indice è indefinito quando il ciclo è completato. Nell'esempio mostrato sotto, il valore scritto dall'istruzione WRITE non è definito nello standard Fortran.

    INTEGER :: i
    DO i = 1, 5
        ...
    END DO
    WRITE (*,*) i
    

    Su molti computer, dopo che il ciclo è completato, la variabile indice i conterrà il primo valore della variabile indice a fallire il test:

    \texttt{index} \cdot \texttt{incr} \leq \texttt{iend} \cdot \texttt{incr}

    Nel codice sopra, il risultato conterrebbe solitamente un 6 dopo che il ciclo è finito. Tuttavia, non bisogna fare assunzioni su tale caratteristica. Poiché il valore è ufficialmente indefinito nello standard Fortran, alcuni compilatori potrebbero produrre un risultato diverso. Se il proprio codice dipende dal valore della variabile indice dopo che il ciclo è completato, si potrebbero ottenere risultati diversi quando il programma viene ricompilato su altre macchine.

    Nota

    Attenzione: Valore Indefinito della Variabile Indice

    Non dipendere mai da una variabile indice per mantenere un valore specifico dopo che un ciclo DO si completa normalmente.