Category Archives: Fortran

Il linguaggio di programmazione Fortran

Argomenti passati a funzione – 2

sicNon so se solo noi… (inteso come quelli con cui lavoravo illo tempore) ma in seguito al post Argomenti passati a funzione ho avuto una sessione di ricordi con il mio boss di allora. Riassumo.

Intanto la funzione corretta (cquad.f):

1
2
3
4
5
6
7
* quad restituisce il quadrato del parametro - corretta
       function quad(x)
       y = x
       y = y * y
       quad = y
       
       end

Verifico, con questo main (c1.f):

1
2
3
4
5
6
7
8
* versione corretta #1
      x = 5
      qx = quad(x)
      print *, ' il quadrato di', x, ' vale ', qx
      qx = quad(8.0)
      print *, ' il quadrato di 8.0 vale ', qx
      
      end 

arg-c1

Nella funzione si copia il parametro in una variabile locale e si modifica solo questa. È così usabile come previsto nel main; qui a raccontarla tutta inizialmente avevo scritto 8 invece di 8.0

Però, mi fa osservare il Signor Capo, la prassi era quella di usare le subroutines, ecco squad.f:

1
2
3
4
       subroutine quad(x)
       x = x * x
       
       end

che verifico con il main c2.f:

1
2
3
4
5
6
* versione corretta #2
      x = 5
      call quad(x)
      print *, ' il quadrato di 5.0 vale ', x
      
      end 

arg-c2

OK, quasi. Ma questo era il modo standard di operare (almeno da noi). Notare che call quad(8.0) non avrebbe senso, non ottengo il risultato (sì un errore, invece).

Una googlata è stata molto fruttuosa: ho trovato il manuale di Prime F77 che riporta in due punti un warning a proposito di questo problema. Ma si sa che nessuno legge i manuali :evil:

pagina 5-1:

5-1

e pagina 5-4

5-4

Abbiamo pensato anche a intent ma ci risulta posteriore, Fortran 90. Può darsi che qualche implementazione l’avesse già allora ma dalla ricerca con Google non ho trovato niente. Trovato invece cose interessanti:

Il commento di Piero Patteri, esaminato con più cura di quanto ho risposto, si riferisce a un altro aspetto ancora, interessa a qualcuno? Devo parlarne? Secondo il Boss il Fortran si usa ancora, anche se molto meno, ci sono concorrenti, tanti. Anche Matlab.


L’immagine lassù è del manuale del Fortran. Trovata su Ebay (in realtà KiJiJi), con questa nota: Nave scuola per migliaia di programmatori, questo volume del 1981 (riedizione di quella del 1971) ha costituito un punto di riferimento per la nascente attività di programmazione per docenti e ricercatori.
E pare sia ancora nel catalogo di Zanichelli. No, solo nel catalogo storico.

Argomenti passati a funzione

pr1meL’altro giorno ero alle prese con la filosofia della programmazione funzionale, cosa di moda ultimamente ma –scopro– vecchia ormai. E mi è tornato in mente un caso di tento tempo fa; quasi-quasi lo racconto. Anzi vado, così verifico se nel frattempo i compilatori sono migliorati. E pensa te che ho dovuto reinstallare il Fortran. Dai c’è di peggio. Per esempio la politica. Il caso è molto semplice. Pochissime righe di codice, antico; qui uso il 77 ma andrebbe bene anche il IV (1966). L’unica cosa da tener presente è che gli argomenti a una funzione sono sempre passati per indirizzo (reference). Quindi quanto si torna dalla chiamata se le variabili usate come parametri sono state modificate risulteranno modificate. Mi prendo una piccola libertà per semplificare il problema: i programmatori allora prediligevano di gran lunga le subroutine, sottoprogrammi che non ritornano esplicitamente un valore ma si affidano ai side effects; invece qui uso una function, quelle del C, c’erano da sempre anche in Fortran. Creo una piccolissima (e ridondante per visualizzare il caso in oggetto) funzione quad() che ritorna il quadrato dell’argomento che le viene passato (quad.f):

1
2
3
4
5
6
7
* quad restituisce il quadrato del parametro
       function quad(x)
       
       x = x * x
       quad = x
       
       end

Iessainou! pessima. Se uno programma così lo licenzio a calcinculo. CMQ, la prima riga è un commento, la quinta specifica il valore da ritornare, calcolata nella riga precedente, inutile, anzi dannosa. Inutile in quanto il calcolo poteva farlo sulla stessa riga che definisce il valore della funzione. Questa funzione può essere chiamata da un main, così (bug1.f):

1
2
3
4
5
6
      x = 5
      print *, 'calcolo il quadrato di X, con X =', x
      qx = quad(x)
      print *, ' il quadrato di', x, ' vale ', qx
      
      end 

Compilo ed eseguo: bug1 Oops! anzi atz! :evil: Ma lo sapevo, x è stata modificata nella funzione. C’era (e forse c’è ancora) di peggio, consideriamo questo caso (bug2.f):

1
2
3
4
5
6
      x = 5
      print *, 'calcolo il quadrato di X, con X =', x
      qx = quad(5)
      print *, ' il quadrato di', x, ' vale ', qx
      
      end 

Compilo, quad è già compilata, devo solo linkarla, risparmio tempo, i vecchi a queste cose erano attenti :wink: ed eseguo: bug2 OK, ecco. Come previsto. A questo punto via con il debug. Cioè no, anzi tutto come previsto. Ma allora… :evil:

L’avvenire del Fortran e i suoi successori

fortran

Forse mi sono lasciato prendere la mano, non so se sia così necessario questo post. Ma siccome l’ho scritto lo pubblico :???:
Tutto è partito da qui: Why Scientists Are Still Using FORTRAN in 2014 che rimanda a Ars technica, qui: Scientific computing’s future: Can any coding language top a 1950s behemoth? dove troviamo

But almost universally, the language in which these simulation codes are written is Fortran, a relic from the 1950s.

e

Wherever you see giant simulations of the type that run for days on the world’s most massive supercomputers, you are likely to see Fortran code.

Poi dice che subito dopo viene inventato il Lisp. Che non ha successo per la sua stranezza:

Weirdness, because the prefix notation used by Lisp made expressions in that language look a lot less like normal mathematics than did math rendered in Fortran. (Fortran stood, after all, for FORmula TRANslator.) A normal chemist or engineer is far more comfortable with y = (a + b)/c than with (setf y ((/ (+ a b) c))).

Ok, l’ho sentita tante volte che mi verrebbe voglia di ignorarla. Invece no! Perché è (quasi) tutto lì. Davvero, ve ne faccio il riassunto.

(setf y (/ (+ a b) c))

Parto dalla parentesi più interna: (+ a b); questa è una lista il cui primo elemento è una funzione. Questa funzione opera sui successivi elementi della lista e li somma. Questa somma (chiamiamola A+B viene restituita: Lisp restituisce sempre l’ultima espressione calcolata.
Quindi la nostra espressione diventa:

(setf y (/ A+B c))

dove (/ A+B c) è una lista in cui il primo elemento è la funzione / che restituisce il rapporto del secondo elemento della lista per tutti i successivi.

Il valore restituito viene assegnato alla variabile y, tramite setf (setf sta per set (quote e sì, si può scrivere anche setq o set ' –non sono esattamente la stessa cosa ma il risultato è lo stesso).

Uh! visto? il Lisp è uniforme. Si comporta sempre allo stesso modo: il primo elemento della lista (chiamato CAR o first) indica come trattare il resto della lista (CDR o rest) e restituisce il risultato.
Per cui il Lisp è fatto da liste dentro liste dentro liste ancora.  Tutto lì (ok, sto semplificando). Non so voi ma a me sembra molto semplice.

Per contro il Fortran riproduce la sintassi matematica che si usa a scuola:

y = (a + b) / c

Sì, lo ammetto, siamo abituati così e per un calcolo semplice come questo meglio il Fortran. Ma per un caso non banale si vede la differenza: con il Lisp ho una concatenazione di liste e con il Fortran una serie di assegnazioni (attenzione il segno = si deve leggere “diventa“, dice il Wirth (che scrive :=)).

11c

Poi una cosa ancora: io sono vecchio e non so nemmeno se si usano ancora ma quando frequentavo il Poli, roba anni ’70, i migliori usavano le calcolatrici HP, con RPNreverse polish notation— quasi come il Lisp (cioè no, esattamente il contrario), nel nostro caso si sarebbe scritto

a
b
+
c
/

Con risparmio di digitalizzazione di tasti –non servono le parentesi–, si consumano meno i diti e –sopratutto– i risultati intermedi sono disponibili nello stack (mi dicono che dovrei usare il termine “pila”).

Ma tornando alla programmazione allora quando è uscito il Fortran era una cosa nuova di pakka, un compilatore per linguaggio facile per gli umani, alternativo alle criptiche istruzioni assembly. E sui calcolatori computer c’era solo il Fortran. Molto diverso da quello di oggi, istruzioni molto elementari, quasi da assembly. E tanti GOTO, spesso mascherati come IF.

Ok, fine dello sfogo, torno al post di Ars Technica secondo il quale ci sono tre candidati a sostituire il Fortran:

Haskell, bello, sintassi da matematici, funzionale (cioè odia le variabili), chissà;
Clojure, Lisp & JVM, sì c’è Java sotto; ho scritto qualcosa in passato;
Julia, nuovissimo, ne so molto poco.

Ma ce ne sono altri, secondo me: il C++, Java, Python e altri ancora. Tutti ampiamente utilizzati.
E i nuovissimi Go e Rust. Di Go sono a conoscenza di un programma funzionante, per il Web, Rust promette bene (ne parla spesso robitex su questo blog).
Inoltre proprio ieri vosto questo post: #golang: The Next Great Teaching Language. Il blog è nuovo ma promette bene. E il post è illuminante.

Invece il Fortran non è amato dai giovani (quelli che conosco), lo trovano vecchio, inusuale. E se possono lo sostituiscono con qualcosa di più pratico, per esempio Matlab (a volte basta la versione free Octave). Ma sto parlando di chi usa il computer come un mezzo complementare. E lo usa per AutoCAD, fogli di calcolo Excel e come macchina da scrivere.

Mi mancano i programmatori di una volta, quando il mondo era nuovo ;-)

Precisione iii

Corto circuito

Questo post è un bellissimo corto circuito: conosco Orlando perché è un amico del GuIT che tra l’altro ho conosciuto l’anno scorso di persona al GuIT meeting di Napoli. E proprio Orlando ha commentato il post di Juhan Precisione su questo stesso blog, di qualche giorno fa su alcuni calcoli.
Questo dimostra che gli appassionati non vedono l’ora di scoprire nuove cose sul proprio argomento preferito.

Chiudo il circuito

Eccomi dunque a chiudere il circuito informatico presentrando un programma in Go che esegue le stesse operazioni eseguite in awk ed in Fortran:

package main

import (
    "fmt"
    "math/big"
)

func main() {
    s, u := big.NewInt(2), big.NewInt(1)
    for i := 1; i <= 10; i++ {
        // a = s - 1
        a := big.NewInt(0).Set(s).Sub(s, u)
        // s*a + 1 = s*(s - 1) + 1
        s.Mul(a, s).Add(s, u)
        fmt.Println(i, s)
    }
}

Tecnicamente, vorrei farvi notare che è possibile in Go concatenare le chiamate dei metodi come per esempio si legge alla riga 12 del codice sorgente, se il metodo stesso ritorna lo stesso oggetto.

Ho utilizzato il pacchetto dei grandi numeri big necessario perché dopo la sesta iterazione i numeri superano il limite massimo del tipo int64. Certo avrei potuto utilizzare il tipo uint64 ma potevo chiudere il circuito soltanto esagerando alla grande portando il ciclo fino a 10 iterazioni… ecco di seguito il risultato:

1 3
2 7
3 43
4 1807
5 3263443
6 10650056950807
7 113423713055421844361000443
8 12864938683278671740537145998360961546653259485195807
9 165506647324519964198468195444439180017513152706377497841851388766535868639572406808911988131737645185443
10 27392450308603031423410234291674686281194364367580914627947367941608692026226993634332118404582438634929548737283992369758487974306317730580753883429460344956410077034761330476016739454649828385541500213920807

A questo punto, ci sta propio bene un
Alla Prossima!
R.

Precisione – 2

precisione
Il post di ieri, Precisione è stato visitatissimo! E Orlando mi chiede una cosa che è difficile far stare in un commento. E allora invece di un commento ecco un intero post.

Orlando pubblica un programma in Fortran questo:

program test
  real :: s=2.0
  do i=1,7
    s=s*(s-1)+1
    write(*,10) s
  end do
10 format(F30.1)
end program test

che produce questo risultato:

                           3.0
                           7.0
                          43.0
                        1807.0
                     3263443.0
              10650057179136.0
 113423716646946804535918592.0

simile ma non uguale a quelli degli script da me pubblicati.
Allora, vediamo. Comincio con modificare lo script di bc, facendogli scrivere i risultai dei singoli passi del ciclo, ecco:

s = 2.0
for (i = 1; i <= 7; i++) {
    s = s * (s - 1) + 1
    print s, "\n"
}
quit

3.0
7.0
43.0
1807.0
3263443.0
10650056950807.0
113423713055421844361000443.0

Dove si vede che le difformità sono negli ultimi due passi, questi:

3.0
7.0
43.0
1807.0
3263443.0
* 10650056950807.0
F 10650057179136.0
         ^ 
* 113423713055421844361000443.0
F 113423716646946804535918592.0
          ^

dove * rappresenta l’output di bc, F quello del Fortran e ^ la prima cifra diversa tra i due. Si vede subito che il numero di cifre per i linguaggi normali (anche il C, dice infatti Orlando) sono 7-8. Più che sufficienti in condizioni normali come concludevo nel post di ieri.
Naturalmente si può fare meglio. In particolare per il programma Fortran ditato sopra basta modificare la seconda riga, così:

real(16) :: s=2.0

e ottengo lo stesso risultato per Fortran e bc (e gli altri programmi, presumibilmente, anzi certamente).

In passato (l’anno scorso) ho raccontato un po’ di cose sul Fortran (per via che si stava facendo manutenzione a cose preistoriche), si trova tutto nell’indice di OK, panico, cercare per Fortran.

In particolare, siccome non ricordo certo cosa ho scritto a giugno del 2012 consiglio una scorsa qui e qui.

Resto disponibile per modifiche e chiarimenti, anzi questo post possiamo considerarlo un prepost ;-)
E, un’ultima cosa (Steve J. finiva sempre così), lo sapete che Orlando ha un sito tutto suo? Questo: Orlando Iovino’s Personal Website. Da finire, certo ;-)

f2py – usare codice Fortran in Python

jakeTutto è partito da qui: Numba vs. Cython: Take 2.

Jake VanderPlas è un tipo tosto, fareste bene a seguirlo. Si presenta così: “I’m an NSF post-doctoral fellow at University of Washington, where I research and teach in Astronomy, Astrophysics, and Machine Learning“.

Jake accenna a f2py. Ehi! potrebbe essere utile a chi ha vecchia roba scritta in Fortran, magari si riesce a metterle un’interfaccia grafica o fare altre mescite di quelle che, fa … beh! Chissà, vediamo.

Intanto salta fuori che f2py è un programma a se stante che compila il codice Fortran (usando il compilatore che hai, dev’essercene uno) creando una libreria (.so), quelle che Python chiama moduli.
Ma è anche un modulo di NumPy, cioè Python. Ed è tutto scritto in Python.
Allora la prima cosa da fare (è da tanto che mi dicevo di farlo) è installare NumPy: f2py è lì. Installare NumPy richiede 952MB di spazio su disco! OK, c’è tempo per un caffè lungo.

Fatto, viene installato anche SciPy, ne ho già parlato in passato. Per vedere comincio a riprodurre l’esempio preso dalla documentazione, questa è la subroutine in Fortan 77:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
C FILE: FIB1.F
      SUBROUTINE FIB(A,N)
C
C     CALCULATE FIRST N FIBONACCI NUMBERS
C
      INTEGER N
      REAL*8 A(N)
      DO I=1,N
         IF (I.EQ.1) THEN
            A(I) = 0.0D0
         ELSEIF (I.EQ.2) THEN
            A(I) = 1.0D0
         ELSE 
            A(I) = A(I-1) + A(I-2)
         ENDIF
      ENDDO
      END
C END FILE FIB1.F

Si crea il modulo Python con il comando:

f2py -c fib1.f -m fib1

e questo è il file di Python:

#!/usr/bin/python
# -*- coding: utf-8 -*-

import numpy as np
import fib1 #quello in Fortran

a = np.zeros(10, 'd')
fib1.fib(a)
print a

f1

OK, vediamo se si può migliorare: Fibonacci restituisce interi, anzi ai tempi di Leonardo Pisano (Fibonacci per i matematti) i float non erano conosciuti. E meno che mai in doppia precisione (come si dice in Fortran).

Le modifiche sono minime:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
C FILE: FIB2.F
      SUBROUTINE FIB(A,N)
C
C     CALCULATE FIRST N FIBONACCI NUMBERS
C versione modificata, uso interi
      INTEGER N
      INTEGER A(N)
      DO I=1,N
         IF (I.EQ.1) THEN
            A(I) = 0.0D0
         ELSEIF (I.EQ.2) THEN
            A(I) = 1.0D0
         ELSE 
            A(I) = A(I-1) + A(I-2)
         ENDIF
      ENDDO
      END
C END FILE FIB2.F

che compilo con

f2-0

f2py riempie il terminale di scritte, roba da preoccuparsi se uno non sapesse che è normale. Ecco lo script Python

#!/usr/bin/python
# -*- coding: utf-8 -*-

#modificato, uso interi

import numpy as np
import fib2 #quello in Fortran

a = np.zeros(10, 'int')
fib2.fib(a)
print a

f2-1

OK. Vediamo qualche caso tipico, non quelli presenti nei tutorial, ma quelli che so che possono capitare, per esempio un “Hello World!” poco funzionale, chissà se funziona.

1
2
3
4
5
      subroutine hw
c scrive il classico saluto

      print *, "Hello World!"
      end

Ho dovuto usare la sintassi vecchia, quella fixed-form, in attesa di leggere il manuale. Ma un passo per volta.

#!/usr/bin/python
# -*- coding: utf-8 -*-

import hw
print "Adesso chiamo il Fortran"
hw.hw()
print "Fatto, OK?"

hw

OK. La pessima abitudine dei programmatori di una volta era di scrivere su file quando capita (visto che mi sto funzionalizzando?), vediamo:

1
2
3
4
5
6
7
8
      subroutine hw2
c scrive il classico saluto

      print *, "Hello World!"
      open(1, file = "prova-f2py.txt")
      write(1, '("Hello World!")')
      close(1)
      end

Per sicurezza ho controllato che con un main in Fortran funzionasse, non si sa mai… È OK.

#!/usr/bin/python
# -*- coding: utf-8 -*-

import hw2
print "Adesso chiamo il Fortran"
hw2.hw2()
print "Fatto, OK?"

hw2

OK, continua a funzionare. E se uno ha una subroutine che ne chiama un’altra in un altro file cosa succede?

1
2
3
4
      subroutine hw3

      call hw2  
      end

La hw2 è la stessa del caso precedente. Il comando per f2py è:

f2py -c hw3.f hw2.f -m hw3

e poi:

#!/usr/bin/python
# -*- coding: utf-8 -*-

import hw3
print "Adesso chiamo il Fortran"
hw2.hw2()
print "Fatto, OK?"

hw3

Chissà se posso passare una stringa al Fortran?

1
2
3
4
5
6
      integer function fs(st)
      character*(*) st

      print *, st
      fs = len(st)
      end
#!/usr/bin/python
# -*- coding: utf-8 -*-

import fs

msg = "f2py rockz!"
print "Adesso chiamo il Fortran"
res = fs.fs(msg)
print res
print "Fatto, OK?"

fs

OK! Un’ultima cosa, poi mi leggo tutto il manuale ma per intanto il free-form, non so se non si possa semplificare, questo è quello che sono riuscito a ottenere finora:

1
2
3
4
5
6
7
8
9
module fhw
  integer i
contains
  subroutine hw
    ! scrive il classico saluto

    print *, "Hello World!"
  end subroutine hw
end module fhw
#!/usr/bin/python
# -*- coding: utf-8 -*-

import hw
print "Adesso chiamo il Fortran"
hw.hw()
print "Fatto, OK?"

hw

No! è solo questione di mettere l’estensione .f90 e funziona tutto:

1
2
3
4
5
subroutine hw
! scrive il classico saluto

print *, "Hello World!"
end subroutine hw
#!/usr/bin/python
# -*- coding: utf-8 -*-

import hw
print "Adesso chiamo il Fortran"
hw.hw()
print "Fatto, OK?"

L’output è uguale al precedente.

Quasi dimenticavo:
NumPy è opera di Travis E. Oliphant, con il cotributo di numerosi collaboratori, vedi Origins of NumPy in Guide to NumPy.
f2py è stato scritto da Pearu Peterson.

Bellissima (anche se l’ho solo scorsa per adesso) la Guide to Numpy, se si ha fretta c’è anche An introduction to Numpy and Scipy. E poi c’è il Cookbook, il sito di SciPy dove ci sono ovviamente NumPy e f2py.

Quindi, conclusione provvisoria: si può usare? Forse, dipende. Leggo il manuale e vi dico.
Prossimamente… forse… ;-)

Sul FORTRAN di EWD

f0A seguito del post su Dijkstra  e di un paio di telefonate mi è venuta voglia di farvi vedere com’era il codice quando ho iniziato a lavorarci io. In realtà sto barando: con il Fortran 77 l’if era ormai multilinea, come if/then/else ma i vecchi (di allora) continuavano a usare il IV e lo imponevano.

Sieve_of_Eratosthenes_animation

L’immagine che illustra il crivello di Eratostene che in questi giorni circola su G+ (credo sia di John Baez Skopp (vedi commento, grazie Annarita!)) è l’ideale per implementare un programmino nel Fortran delle origini. Eccolo

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
C     ERAHOSTHENES

      DIMENSION NUMERI(20), LPRIMI(10)

C     NUMERI CONTIENE I NUMERI CHE POSSONO ESSERE PRIMI  
      DO 10, I = 1, 20
10    NUMERI(I) = I
      NUMERI(1) = 0  

C     NPRIMI = IL NUMERO DI PRIMI TROVATI
      NPRIMI = 0
      NTROV = 2

C     QUANDO NE TROVO UNO LO AGGIUNGO ALLA LISTA DEI PRIMI
20    NPRIMI = NPRIMI + 1
      LPRIMI(NPRIMI) = NTROV

C     ELIMINO I SUOI MULTIPLI NELLA LISTA DEI NUMERI
      J = 1 
30    J = J + 1
      NT = NTROV * J
      IF(NT .GT. 20) GOTO 40
      NUMERI(NT) = 0
      GOTO 30

C     PASSO ALL ESAME DEL CANDIDATO SUCCESSIVO
40    N = NTROV
41    N = N + 1
      IF(N .GT. 20) GOTO 60
      IF(NUMERI(N) .EQ.0) GOTO 41
50    NTROV = N
      GOTO 20

C     FINE DELL ESAME DELLA LISTA
60    DO 65 I = 1, NPRIMI
65    WRITE(*, 90) I, LPRIMI(I)
90    FORMAT(2I4)

      END

ERAT

Nello spirito del tempo ho scritto tutto in maiuscolo ;-) Però non ce l’ho fatta a non lasciare spazi: allora si sarebbe scritto tutto come J = J + 1. E un’altra cosa ancora: la riga a cui l’esecuzine saltava a seguito di un GOTO era tipicamente del tipo:

50    CONTINUE

essendo quest’istruzione segnaposto fatta proprio apposta per questo. Ma il mio programma sarebbe diventato troppo lungo, inutilmente.
Un’altra cosa: è quasi immediata l’ottimizzazione considerando che 2 è l’unico primo pari, dimezzando il numero di numeri da testare ma il codice si allunga, lasciato come esercizio.

Ecco, allora si programmava così. Non ho più il libro ma nel testo che si usava al Poli c’era scritto: “finito di perforare le schede fai una stampa del listato e controllala attentamente prima di provare l’esecuzione“, vero anche perché a me al primo tentativo è successo questo:

0

Avevo sbagliato il numero di una label.
Bon! Nostalgia OFF! :-D

lcm – Fortran e linguaggi normali

Nuova puntata della telenovela lcm, le precedenti sono qui e qui.
Perché ci sono un paio di cose nuove (per me almeno) da dire sull’argomento. Roba che non finisce oggi, forse, credo.

lcm-matFinora ho utilizzato linguaggi che rendono il problema facile, anche se non hanno una core function che lo implementa.

Mi interessava vedere i linguaggi normali, tipo C/C++, Java, Go, Io che sono vecchio mi sono detto: e il Fortran? Recentemente ha subito una cura ringiovanente, è diventato come gli altri, indistinguibile (quasi).
Verificare è facile, c’è Google. Ho trovato questo: FORTRAN PROGRAMS FOR SOLVIMG NUMERICAL PROBLEMS, Designed by T K Rajan.
Bello, ci sono tutte (o parecchie) delle cose che servivano e che ogni programmatore Fortran aveva una volta. c’è anche lui, l’lcm, a p.7. Vediamo:

PROGRAM gcdlcm
integer a,b,l

write(*,*)'To find the gcd and lcm of two numbers.'
write(*,*)'Enter 2 numbers:'
read(*,*)a,b

m=a
n=b
IF(a.GT.b)THEN
    j=a
    a=b
    b=j
ENDIF
10  i=mod(b,a)
    IF(i.EQ.0)THEN
        write(*,20) a
20      format(1x,'GCD= ',I4)
    ELSE
        b=a
        a=i
        GOTO 10
    ENDIF

l=m*n/a
write(*,30)l
30 format(1x,'LCM= ',I4)

END

l0

Prima delusione: è scritto come si usava una volta, diciamo Fortran 77, 1978 e via fino al ’90. Provo a attualizzarla un pochino, ecco la mia versione:

program lcm

implicit none
integer a, b, i, l, m, n

print *, 'Enter 2 numbers:'
read(*,*) a, b

m = a
n = b
if(a > b) then
    i = a
    a = b
    b = i
end if

i = mod(b, a)
do while(i /= 0)
    b = a
    a = i
    i = mod(b, a)
end do
print *, 'GCD = ', a

l = m * n / a
print *, 'lcm = ', l

end program

Però, cosa più grave, siamo sempre limitati a due numeri. E non abbiamo l’equivalente di reduce() o apply(). Forte di quello visto in precedenza mi son detto: “si può fare”. Anzi è risultato semplicissimo, un ciclo for do (in Fortran il for si chiama do), righe 14-17.

program lcm_ftn

implicit none

integer :: i, numero_dati, dati(10)
integer :: lcm, lcm_2

call leggi_input(numero_dati, dati)
if (numero_dati < 2) then
    print *, 'dati insufficienti'
    call exit(1)
end if

lcm = dati(1)
do i = 2, numero_dati
    lcm = lcm_2(lcm, dati(i))
end do

print '(a6, i4)', 'lcm = ', lcm

end program

subroutine leggi_input(narg, arg)
implicit none

character(20) :: argv ! argomenti linea di comando
integer :: narg ! numero argomenti
integer :: i, t, arg(10)

narg = command_argument_count()
do i = 1, narg
    call get_command_argument(i, argv)
    read(argv, *), arg(i)
end do

end subroutine

integer function lcm_2(a, b) result(res)
implicit none

integer :: a, b, i, m, n

m = a
n = b
if(a > b) then
    i = a
    a = b
    b = i
end if

i = mod(b, a)
do while(i /= 0)
    b = a
    a = i
    i = mod(b, a)
end do

res = m * n / a

end function

lcmftn

OK, fatto. Semplice vero? E si capisce perché non ci sia nei linguaggi normali. Avevo pensato di rifarlo anche in awk (come Bit3Lux sono affascinato da awk, e io ho cominciato prima) ma poi ho pensato che era solo una trascrizione per l’algoritmo complicato dalla necessità di usare un linguaggio nato per script one-liner (o poco più). Nel mio caso le righe di codice sarebbero state parecchie.

C’è anche un altro motivo: sapete com’è, con Google, a volte, quando meno te l’aspetti…
Ma non voglio anticipare l’argomento della prossima puntata (anche se JeanMM…) ;-)

Non so se avete notato che ho sempre parlato di righe e mai di schede (almeno credo di averle corrette tutte).

Fortran, common o moduli?

Ecco la seconda puntata della telenovela, iniziata qui.


È possibile continuare a usare i common per le variabili globali o conviene passare ai moduli?

Dal punto di vista prestazionale non ci sono differenze, come vedremo tra breve, ma dal lato logico le cose cambiano. Vediamo un piccolo esempio.

Ecco la versione che utilizza i common (oldc):

program oldc
implicit none

call fillmat
call showval

end

subroutine fillmat
implicit none

integer, parameter :: nmax = 10000
integer :: mat(nmax, nmax)
common /bigmat/ mat

integer :: r, c

do c = 1, nmax
    do r = 1, nmax
        if (r >= c) then
            mat(r, c) = r * c
        else
            mat(r, c) = 0
        end if
    end do
end do

end

subroutine showval
implicit none

integer, parameter :: nmax = 10000
integer :: mat(nmax, nmax)
common /bigmat/ mat

print *, mat(nmax, 1), mat(nmax, nmax), mat(1, nmax)

end

Notare che il parameter nmax non si può mettere nel common, anche se si riferisce a questo. Converrebbe mettere in un file include le righe [12..14], ripetute a [33..35] per evitare errori in aggiornamento.

Lo stesso programma riscritto utilizzando un modulo diventa:

program newm
use bigmatm

implicit none

call fillmat
call showval

end program

 

module bigmatm
implicit none

integer, parameter :: nmax = 10000
integer :: mat(nmax, nmax)

contains
    subroutine fillmat
        integer :: r, c

        do c = 1, nmax
            do r = 1, nmax
                if (r >= c) then
                    mat(r, c) = r * c
                else
                    mat(r, c) = 0
                end if
            end do
        end do

    end subroutine fillmat

    subroutine showval

        print *, mat(nmax, 1), mat(nmax, nmax), mat(1, nmax)

    end subroutine showval

end module bigmatm

Per comodità il modulo è su un file separato (cosa normale per i programmi veri, molto più lunghi di questo esempio minimale). In questo caso le caratteristiche della matrice sono specificate una sola volta, diminuisce la possibilità di errori. Inoltre le due subroutine risultano private al modulo (e a chi lo usa).

Per contro le due versioni sono equivalenti rispetto al tempo di esecuzione. Io sostengo l’uso dei moduli, in qualche misura cambia anche l’impostazione logica del problema. Per contro e possibile mischiare codice vecchio, con common, definizione implicita delle variabili, formato fisso e quant’altro con il nuovo standard. Ma quello che si deve ancora scrivere, dai, facciamolo corrente.

Ci sarebbe poi un’ulteriore questione, alla quale non so rispondere, anzi benvenuti i suggerimenti. Si è deciso di usare il Fortran per un paio di motivi: c’è già parecchia roba scritta in Fortran che gira qui e poi perché il Fortran è abbastanza simile al Basic (una volta si diceva che il Basic era un Fortran semplificato). Poi è gratis. Il problema è che per averlo su Windows occorre installare Cygwin e pare non sia così immediato. Dai test effettuati abbiamo visto che Java è veloce e la JVM c’è dappertutto. Allora non converrebbe fare tutto in Java?

Ordine per cicli nidificati e trasposizione matrici

Credevate che fosse finita la telenovela del Fortran, invece no!
Di ritorno dalle ferie si riprende con due puntate: * 2 puntate 2 *, ma brevissime. Questa è la prima.

La questione è un classico ma siccome salta fuori continuamente lo ripeto ancora una volta. L’ultima! (A meno che…).

Supponiamo di avere una matrice bidimensionale, per esempio punti X, Y del piano cartesiano, come conviene gestirli?
Il Fortran, a differenza degli altri linguaggi (C/C++, Basic, Python, tutti) ordina i dati per colonna. Fintanto che i dati sono pochi la differenza tra la versione corretta e quella usuale è oggi, con le macchine attuali, trascurabile. Diventa però sensibile e poi non è bello.

Ecco un esempio, minimo, didascalico. Talmente didascalico che lo riciclerò tutte le volte che la questione salta fuori. (Capita sovente dopo le ferie).

Le due versioni seguenti producono lo stesso risultato: rempiono la parte sopra la diagonale principale (compresa) con la somma degli indici di riga e colonna. Con una differenza: la versione per colonne è 3.2 volte più veloce!

program righe
implicit none

integer, parameter :: nmax = 10000
integer :: r, c
integer :: mat(nmax, nmax)

do r = 1, nmax
    do c = 1, nmax
        if (r >= c) then
            mat(r, c) = r * c
        else
            mat(r, c) = 0
        end if
    end do
end do

! per controllo
print *, mat(nmax, 1), mat(nmax, nmax), mat(nmax, 1)

end

program colonne
implicit none

integer, parameter :: nmax = 10000
integer :: r, c
integer :: mat(nmax, nmax)

do c = 1, nmax
    do r = 1, nmax
        if (r >= c) then
            mat(r, c) = r * c
        else
            mat(r, c) = 0
        end if
    end do
end do

! per controllo
print *, mat(nmax, 1), mat(nmax, nmax), mat(1, nmax)

end

OK, si tratta di secondi ma voglio solo vedere quella giusta. Avvisati nèh!

Però metti che io la mia matrice la voglio usare in un programma scritto, per esempio, in C++; devo leggere i dati in modo innaturale?
No! il Fortran ha pensato anche a te e a questo tuo caso: esiste la funzione transpose(). Visualizzarne il funzionamento con una matrice così grossa sarebbe problematico, diciamo che la riduciamo a 10 * 10, ecco:

program inverti
implicit none

integer, parameter :: nmax = 10
integer :: r, c
integer :: mat(nmax, nmax), inv(nmax, nmax)

do c = 1, nmax
    do r = 1, nmax
        if (r >= c) then
            mat(r, c) = r * c
        else
            mat(r, c) = 0
        end if
    end do
end do

inv = transpose(mat)

print '(10i4)' , mat
print *
print '(10i4)' , inv

end

Nota: non è possibile sovrascrivere direttamente la matrice, quest’istruzione è errata: mat = transpose(mat).

Prossimamente la seconda (forse ultima) puntata, altrettanto –come si dice– ecco ;-) :-o :-D

Iscriviti

Ricevi al tuo indirizzo email tutti i nuovi post del sito.

Unisciti agli altri 87 follower