Insidie nei programmi di calcolo

Nel mio articolo sul Software libero per calcolare ho accennato alla delicatezza del software di calcolo ed all’esistenza di alcune trappole annidate nei vari linguaggi di programmazione, con il pericolo che il programma che utilizziamo per fare un calcolo fornisca un risultato sbagliato senza che ce ne accorgiamo.
Al fine di mettere in guardia il programmatore dilettante che voglia cimentarsi nel costruire qualche strumento di calcolo a suo uso e consumo per calcolare, ad esempio, la temperatura percepita nota la temperatura e l’umidità relativa (o altre cose che non troviamo facilmente in programmi già predisposti), ho pensato di indicare in questo articolo quali sono le due principali fonti di complicazione: la questione del separatore decimale e quella della divisione tra interi.

Il separatore decimale

In tutti i linguaggi di programmazione il separatore decimale è il punto (.) ed è questo che deve essere rigorosamente usato nelle assegnazioni interne ai programmi.
Il problema si pone, specialmente per noi italiani abituati ad usare come separatore decimale la virgola, quando è richiesto un input numerico all’utente, in quanto egli, anche se avvisato sul separatore decimale da utilizzare, può sbagliare e ciò può avere, in certi casi, conseguenze disastrose sul risultato dell’elaborazione compiuta dal programma.
I linguaggi più sicuri da questo punto di vista sono Ada, Java, Pascal e Python in quanto in questi linguaggi è imposto un input con separatore decimale punto e, se l’utente usa la virgola, viene elevata una eccezione di input e non viene fornito alcun risultato. Attraverso una opportuna gestione di questa eccezione è possibile avvisare l’utente dell’errore ed invitarlo a formulare un input corretto: il tutto senza uscire dal programma. Se l’eccezione non viene gestita il programma termina senza risultato e il danno è circoscritto a questo.
I linguaggi più subdoli sono il C, il C++ e PHP in quanto impongono il punto ma, se l’utente usa la virgola, non sollevano eccezioni di input, ignorano ciò che viene immesso dopo la virgola e forniscono risultati sballati: il tutto senza che l’utente sappia alcunché, né sull’errore compiuto usando la virgola in luogo del punto né – il che è gravissimo – sul fatto che il risultato che gli è stato fornito dal programma è sbagliato. Unico rimedio a tutto ciò è un accurato controllo dell’input, carattere per carattere, in modo da rimediare alla falla del linguaggio con accorgimenti di programmazione che sollevino un’eccezione gestibile nel momento in cui nell’input venisse trovata una virgola. Eccezione gestibile fino a sostituire un punto alla virgola, nel caso la si trovasse, senza dire nulla all’utente, ed arrivare comunque ad un risultato corretto: in questo modo possiamo avere un programma per il quale va bene l’uso di entrambi i separatori decimali, punto e virgola.
Vi sono, infine, i linguaggi del gruppo Visual Studio di Microsoft, dove i problemi non mancano, anzi…..
Visual C++ di Visual Studio si comporta esattamente come il normale C++.
Visual C# di Visual Studio ha la particolarità che, quando viene caricato su un computer, si adegua alla cultura dell’utente: se il sistema operativo installato è una versione italiana, anche Visual C# sarà italiano. Tra le caratteristiche di questa cultura c’è il fatto che l’utente usa come separatore decimale la virgola e C# si adegua: al suo interno usa rigorosamente il punto come separatore decimale ma quando legge un input dell’utente italiano si aspetta di trovare, come separatore decimale, la virgola. Se l’utente usa il punto, questo viene semplicemente ignorato e il numero viene letto come un intero composto da tutte le cifre immesse, sia quelle prima che quelle dopo il punto e l’elaborazione prosegue con questo numero, con tutte le disastrose conseguenze del caso.
Visual Basic, sia nella vecchia versione, sia in quella più aggiornata, è il massimo della confusione. Anch’esso, al momento dell’installazione, si adegua alla cultura dell’utente ma lo fa parzialmente: mentre, infatti, la funzione Cdbl() per convertire in numero la stringa immessa dall’utente si adegua alla cultura di questi e, se egli usa un sistema operativo in italiano, si aspetta di trovare la virgola come separatore decimale e, se trova il punto, fa avvenire esattamente quello che avviene in C#, la funzione Val(), utilizzabile per lo stesso scopo, si aspetta di trovare comunque il punto come separatore decimale e, se trova una virgola, non legge oltre ed incamera un numero intero composto dalle sole cifre immesse dall’utente prima della virgola e prosegue così il suo percorso di elaborazione. Visual Basic, pertanto, non è solo subdolo ma è anche ambiguo.
Fortunatamente le versioni di Visual C# e di Visual Basic delle più recenti edizioni di Visual Studio hanno la funzione membro Replace() dell’oggetto stringa che, con poca spesa, ci aiuta a correggere l’input in modo da poter accettare invariabilmente sia il punto che la virgola dall’utente (sempre, in Visual Basic, stando attenti alla correzione da apportare in coerenza a che cosa si usa per convertire la stringa).

Divisione tra interi

L’uso dell’operatore / tra due numeri fornisce come risultato il quoziente di una divisione.
Questo risultato è il quoziente vero, qualunque sia il tipo di numero indicato prima e dopo l’operatore /, solo nei linguaggi Pascal, Python 3 e Visual Basic (sia nella vecchia che nella nuova versione). Per quoziente vero si intende il 2,5 che risulta da 5 / 2.
Nei linguaggi C, C++ e C# se i numeri indicati prima e dopo l’operatore / sono interi si ha una divisione tra interi che fornisce come risultato la parte intera del quoziente. Cioè 5 / 2 fornisce il risultato 2. Purtroppo ciò avviene anche quando la variabile cui assegnare il risultato sia stata dichiarata come double, senza che vi sia alcuna segnalazione di errore. Per ottenere il quoziente vero occorre che almeno uno dei due numeri inseriti prima e dopo l’operatore / sia un double (basta scrivere 5.0 anziché 5).
In Java questo inconveniente si presenta soltanto se l’espressione di divisione è inserita in una istruzione di stampa, senza passare attraverso l’assegnazione a variabili. L’istruzione System.out.println(5/2) stampa 2 e, per ottenere la stampa del risultato corretto occorre scrivere System.out.println(5/2.0) o System.out.println(5.0/2). Non è invece possibile, per sollevamento di eccezione nella compilazione, assegnare il risultato di 5/2 ad una variabile dichiarata double, il che, almeno in parte, attutisce i pericoli che Java ha ereditato dal C.
Allo stesso modo di Java si comporta Ada con la sola eccezione che la divisione inserita in una istruzione di stampa, perchè dia il risultato corretto deve avere sia prima che dopo l’operatore / un float. Cioè non basta dire Put(5.0/2) o Put(5/2.0) ma occorre dire Put(5.0/2.0): in caso contrario viene segnalato errore nella compilazione.
Python 2 è nella stessa situazione di Java se l’input è acquistito con la funzione raw_input(), in quanto tutto si sistema se con la conversione della stringa acquisita si memorizza il valore in una variabile float. Se l’input è acquisito con la funzione input(), dal momento che questa, in Python 2, ritorna un valore numerico, occorre che almeno uno dei due numeri sia scritto come float dalla tastiera.