Robitex's Blog
Ideas in the web
Numeri in lettere
Pubblicato da su 21/04/2011
Scarica l’articolo in formato pdf per la stampa
Un problema antico: tradurre numeri in lettere
Molti anni fa acquistai un libro che proponeva una serie di esempi, di esercizi, tutti risolti con il linguaggio Basic.
Nonostante il fatto che quel libro una volta prestato non venne più restituito (ebbene l’interessato/a ovunque esso/a sia, è ancora in tempo per restituire quel libro e, già che c’é, anche gli altri libri di storia dell’arte, grazie), ricordo che verso la fine esso presentava il problema di tradurre un numero in lettere, per esempio, se l’input fosse stato 123 allora l’output del programma avrebbe dovuto essere “centoventitre”.
Soluzione in Java
Malauguratamente il linguaggio Java non è in grado di far “ritrovare” i libri “perduti” ai legittimi lettori (anche se credo che Oracle stia lavorando a funzionalità del genere), ma è comunque in grado di dare soddisfazione a chi volesse tradurre vaghi e primordiali ricordi, in una pagina web di un pugno di bit.
Cominciamo allora con qualcosa di molto più sofisticato di quello che si trovava nel codice Basic di quel libro notando che il numero 123 si può tradurre in lettere in tempi successivi scrivendo prima “cento” e continuando con il numero rimanente 23, scrivendo poi “venti” e continuando ancora con il numero 3, traducendolo in “tre”. Se pensiamo di unire le tre parole avremo ottenuto “centoventitre”.
Se il numero da tradurre in parole fosse invece 6123, dapprima scriveremmo “seimila” e poi ritrovando ancora il numero 123 applicherei la stessa procedura precedente ed alla fine otterrei la sequenza “seimilacentoventitre”.
Adotteremo quindi un approccio ricorsivo per cui elaborata la cifra più significativa si lancia la procedura stessa con il numero restante. Il codice che compare di seguito evita i casi di doppia vocale. Applicando strettamente la procedura ricorsiva, il numero 41 diventerebbe “quarantauno” invece di “quarantuno”. Inoltre, grazie all’overloading dei metodi, il programma è in grado di elaborare numeri decimali traducendoli nel formato letterale previsto per gli importi in euro.
import java.util.Scanner;
public class Traduci {
// aggiunto il metodo in overloading che fa
// corrispondere una cifra in euro alla stringa convenzionale
// es.: 58,45 -> "cinquantotto/45"
public static void main(String[] args) {
System.out.print("Digita il numero da tradurre: ");
Scanner in = new Scanner(System.in);
if (in.hasNextInt()) {
System.out.println("in lettere: " +
Traduci.NumberToText(in.nextInt()));
} else if (in.hasNextDouble()) {
System.out.println("in lettere: " +
Traduci.NumberToText(in.nextDouble()));
} else {
System.out.println("Errore nei dati immessi.");
}
}
static String NumberToText(int n) {
// metodo wrapper
if (n == 0) {
return "zero";
} else {
return NumberToTextRicorsiva(n);
}
}
static String NumberToText(double importo) {
// metodo wrapper
int n = (int) Math.round(importo * 100);
int parteIntera = n/100;
String parteDecimale = String.format("%02d", n%100);
if (parteIntera == 0) {
return "zero/" + parteDecimale;
} else {
return NumberToTextRicorsiva(parteIntera) + "/" + parteDecimale;
}
}
private static String NumberToTextRicorsiva(int n) {
if (n < 0) {
return "meno " + NumberToTextRicorsiva(-n);
} else if (n == 0){
return "";
} else if (n <= 19){
return new String[] { "uno", "due", "tre", "quattro", "cinque",
"sei", "sette", "otto", "nove", "dieci",
"undici", "dodici", "tredici",
"quattordici", "quindici", "sedici",
"diciassette", "diciotto", "diciannove" }[n-1];
} else if (n <= 99) {
String[] vettore =
{ "venti", "trenta", "quaranta", "cinquanta", "sessanta",
"settanta", "ottanta", "novanta" };
String letter = vettore[n / 10 - 2];
int t = n % 10; // t è la prima cifra di n
// se è 1 o 8 va tolta la vocale finale di letter
if (t == 1 || t == 8 ) {
letter = letter.substring(0, letter.length() - 1);
}
return letter + NumberToTextRicorsiva(n % 10);
} else if (n <= 199){
return "cento" + NumberToTextRicorsiva(n % 100);
} else if (n <= 999){
int m = n % 100;
m /= 10; // divisione intera per 10 della variabile
String letter = "cent";
if (m != 8){
letter = letter + "o";
}
return NumberToTextRicorsiva(n / 100) + letter +
NumberToTextRicorsiva(n % 100);
}
else if (n <= 1999){
return "mille" + NumberToTextRicorsiva(n % 1000);
} else if (n <= 999999){
return NumberToTextRicorsiva(n / 1000) + "mila" +
NumberToTextRicorsiva(n % 1000);
}
else if (n <= 1999999){
return "unmilione" + NumberToTextRicorsiva(n % 1000000);
} else if (n <= 999999999){
return NumberToTextRicorsiva(n / 1000000) + "milioni" +
NumberToTextRicorsiva(n % 1000000);
} else if (n <= 1999999999){
return "unmiliardo" + NumberToTextRicorsiva(n % 1000000000);
} else {
return NumberToTextRicorsiva(n / 1000000000) + "miliardi" +
NumberToTextRicorsiva(n % 1000000000);
}
}
}
Una volta inserito il codice in un file di testo chiamato “Traduci.java”, procedete alla sua compilazione utilizzando uno JDK 5.0 o successivo con il comando (installate eventualmente da voi un JDK scelto tra le varie alternative):
javac Traduci.java
Per lanciare il programma che consiste nel file contenente il bytecode per la macchina virtuale Java ed ha per estensione .class, date il comando:
java Traduci
ed inserite il numero intero o decimale da tradurre in parole. Nell’immagine seguente potete consultare la sessione di compilazione ed esecuzione.
Soluzione in Lua
Vogliamo adesso scrivere lo stesso programma in Lua, non tanto per confrontarlo con la versione Java, quanto per predisporne l’uso con LuaTeX. Il codice in Lua è molto meno strutturato di quello in Java e ha un sapore, diciamo così, più artigianale ma è più semplice, eccolo:
#!/usr/bin/lua
-- script di traduzione di numeri in lettere
-- nella lingua italiana
-- traduce un numero in lettere
-- funzione ricorsiva
local function NumberToTextRicorsiva(n)
if n < 0 then
return "meno " .. NumberToTextRicorsiva(-n)
elseif n == 0 then
return ""
elseif n <= 19 then
local ventina = { "uno", "due", "tre", "quattro", "cinque",
"sei", "sette", "otto", "nove", "dieci",
"undici", "dodici", "tredici",
"quattordici", "quindici", "sedici",
"diciassette", "diciotto", "diciannove" }
return ventina[n]
elseif n <= 99 then
local decine = { "venti", "trenta", "quaranta",
"cinquanta", "sessanta",
"settanta", "ottanta", "novanta" }
local letter = decine[math.floor(n/10)-1]
local t = n % 10
if t == 1 or t == 8 then
letter = string.sub(letter,1,-2)
end
return letter .. NumberToTextRicorsiva(n % 10)
elseif n <= 199 then
return "cento" .. NumberToTextRicorsiva(n % 100)
elseif n <= 999 then
local m = n % 100
m = math.floor(m/10)
local letter = "cent"
if m ~= 8 then
letter = letter .. "o"
end
return NumberToTextRicorsiva( math.floor(n / 100)) ..
letter ..
NumberToTextRicorsiva(n % 100)
elseif n<= 1999 then
return "mille" .. NumberToTextRicorsiva(n % 1000)
elseif n<= 999999 then
return NumberToTextRicorsiva(math.floor(n / 1000)) ..
"mila" ..
NumberToTextRicorsiva(n % 1000)
elseif n <= 1999999 then
return "unmilione" .. NumberToTextRicorsiva(n % 1000000)
elseif n <= 999999999 then
return NumberToTextRicorsiva(math.floor(n / 1000000))..
"milioni" ..
NumberToTextRicorsiva(n % 1000000)
elseif n <= 1999999999 then
return "unmiliardo" .. NumberToTextRicorsiva(n % 1000000000)
else return NumberToTextRicorsiva(math.floor(n / 1000000000)) ..
"miliardi" ..
NumberToTextRicorsiva(n % 1000000000)
end
end
-- funzione wrapper
local function NumberToText(n)
if n == 0 then
return "zero"
else
return NumberToTextRicorsiva(n)
end
end
-- prelevo il valore numerico come argomento dello script
local num = arg[1]
-- gestisco il caso di numero con decimali
local dec = num - math.floor(num)
local tail = ""
if dec ~= 0 then
dec = dec * 100
tail = "/" .. string.format("%.0f",dec)
end
print("in lettere: ".. NumberToText(math.floor(num))..tail)
Soluzione in LuaTeX
Adesso vediamo come incorporare il programma in un documento LuaTeX, ovvero in un file di testo che passato come argomento al motore di composizione tipografica LuaTeX, produce un file pdf con il contenuto voluto.
Come per gli altri motori tipografici della famiglia TeX, anche in LuaTeX il testo viene direttamente composto nel documento a meno che non sia preceduto dal carattere di backslash: “\”. Questo particolare carattere viene interpretato come simbolo di escape nei confronti del testo che lo segue. Così se TeX incontra il testo:
Il numero 123456789 in lettere viene scritto come \traduci{123456789}.
allora inserirà nel documento finale tutti i caratteri tranne per \traduci che viene inteso invece come una macro. Questo è il succo di uno dei linguaggi tipografici più noti al mondo.
Dunque basterà associare la corretta definizione della macro \traduci per veder comparire automaticamente nel pdf il numero tradotto in lettere scritto tra parentesi graffe con il significato di argomento. Il codice Lua deve essere leggermente modificato, primo perché l’operatore modulo corrisponde al carattere di commento di TeX per cui lo esprimeremo in termini matematici (si tratta del resto della divisione intera). Secondo, dobbiamo trasformare i simboli di commento Lua nel simbolo di commento TeX e terzo, occorre premettere al simbolo di tilde che compare nell’operatore di disuguaglianza la macro primitiva \string per evitare che venga interpretato diversamente. Ecco il sorgente LuaTeX per intero:
\directlua{
function mod(n,d)
return n-d*math.floor(n/d)
end
}
\def\traduci#1{%
\directlua{
local function NumberToTextRicorsiva(n)
if n < 0 then
return "meno " .. NumberToTextRicorsiva(-n)
elseif n == 0 then
return ""
elseif n <= 19 then
local ventina = { "uno", "due", "tre", "quattro", "cinque",
"sei", "sette", "otto", "nove", "dieci",
"undici", "dodici", "tredici",
"quattordici", "quindici", "sedici",
"diciassette", "diciotto", "diciannove" }
return ventina[n]
elseif n <= 99 then
local decine = { "venti", "trenta", "quaranta",
"cinquanta", "sessanta",
"settanta", "ottanta", "novanta" }
local letter = decine[math.floor(n/10)-1]
local t = mod(n, 10)
if t == 1 or t == 8 then
letter = string.sub(letter,1,-2)
end
return letter .. NumberToTextRicorsiva(mod(n,10))
elseif n <= 199 then
return "cento" .. NumberToTextRicorsiva(mod(n,100))
elseif n <= 999 then
local m = mod(n,100)
m = math.floor(m/10)
local letter = "cent"
if m \string~= 8 then
letter = letter .. "o"
end
return NumberToTextRicorsiva( math.floor(n / 100)) ..
letter ..
NumberToTextRicorsiva(mod(n,100))
elseif n<= 1999 then
return "mille" .. NumberToTextRicorsiva(mod(n,1000))
elseif n<= 999999 then
return NumberToTextRicorsiva(math.floor(n / 1000)) ..
"mila" ..
NumberToTextRicorsiva(mod(n,1000))
elseif n <= 1999999 then
return "unmilione" .. NumberToTextRicorsiva(mod(n,1000000))
elseif n <= 999999999 then
return NumberToTextRicorsiva(math.floor(n / 1000000))..
"milioni" ..
NumberToTextRicorsiva(mod(n,1000000))
elseif n <= 1999999999 then
return "unmiliardo" .. NumberToTextRicorsiva(mod(n,1000000000))
else return NumberToTextRicorsiva(math.floor(n / 1000000000)) ..
"miliardi" ..
NumberToTextRicorsiva(mod(n,1000000000))
end
end
% funzione wrapper
local function NumberToText(n)
if n == 0 then
return "zero"
else
return NumberToTextRicorsiva(n)
end
end
% prelevo il valore numerico come argomento dello script
local num = #1
% gestisco il caso di numero con decimali
local dec = num - math.floor(num)
local tail = ""
if dec \string~= 0 then
dec = dec * 100
tail = "/" .. string.format("\%.0f",dec)
end
tex.sprint(NumberToText(math.floor(num))..tail)
}}
Il numero 123456789 in lettere viene scritto come \traduci{123456789}.
Ciao
\bye
Compilatelo dopo averlo inserito in un file chiamato trad.tex e compilatelo con il comando da console: luatex trad (occorre che una distribuzione recente del sistema TeX sia installata sul vostro sistema, per esempio TeX Live).
Se volete potete scaricare da questo link il pdf risultato.
Come breve riferimento, posso aggiungere che nel codice LuaTeX la definizione della macro \traduci si avvale della nuova primitiva \directlua che esegue codice Lua, in cui si possono elaborare argomenti rappresentati con il simbolo #1 (per il primo di essi), proprio come un normalissimo sorgente TeX. Inoltre, la funzione di sostituzione dell’operatore modulo viene caricata in memoria in una prima macro \directlua dimostrando come in LuaTeX le varie porzioni di codice distribuite tra le varie \directlua del sorgente, condividono lo spazio delle variabili globali.
Ci tengo a precisare che l’operazione di traduzione di numeri in lettere è già stata brillantemente risolta per la lingua italiana dal Prof. Enrico Gregorio con il suo pacchetto itnumpar utilizzando esclusivamente codice TeX. Questo pacchetto tiene conto degli accenti delle parole delle cifre e della eventuale necessità dell’iniziale maiuscola. Esso è stato scritto per l’elaborazione dei titoli di capitolo per esempio per far uscire il testo “Capitolo Diciotto” anziché del solito “Capitolo 18″.
Bene. Ho terminato questo post ricco di codice. Come sempre sono aperti i commenti per aggiungere, segnalare o fare domande. Alla prossima.
Ciao.

Ciao,
seguo sempre il tuo blog perché offri sempre dei buoni esempi di come usare TeX ed affini in modi non banali.
In questo ultimo periodo mi sto avvicinando a LuaTeX (e quindi a LuaLaTeX). MI è un po’ difficile perché non conosco niente di questo linguaggio….. ma rimiederò.
Comunque compilando il tuo esempio, il file log mi segnala questo errore:
**trad
(./trad.tex
! LuaTeX error :1: ‘=’ expected near ‘dec’.
\traduci …NumberToText(math.floor(num))..tail) }
l.86 …ere viene scritto come \traduci{123456789}
Che combino di sbagliato?
Ciao Ciao
Ciao Michele.
Vero. Il sorgente LuaTeX conteneva un errore: mancava una i per un costrutto if. Grazie per la segnalazione. Ho corretto e riverificato il file.
Volevo chiederti, cosa intendi quando dici che LuaTeX è difficile: è perché non conosci bene Lua o non conosci TeX?
Grazie e Buona Pasqua.
Un quesito:
il codice restituisce una traduzione non corretta per i numeri negativi, riuscite a spiegarne il motivo?
Bye
Non conosco Lua…. ma ho rimediato comprando il manuale.
A breve mi dovrebbe arrivare.
Inoltre il manuale di LuaTeX che si trova sul sito ufficiale non aiuta molto….
Per quanto riguarda TeX, mi ci sono addentrato ultimamente(3-4 mesi) visto che fino a poco fa ero solo un “utilizzatore finale” di LaTeX.
Il mio sogno sarebbe essere così bravo da programare una serie di commandi tipo Sketch, programma introdotto da De Agostino al Guit Meeting 2009 per disegnare bellissime figure, in Lua da scrivere dentro un’ambiente tikzpicture in modo che esso elabora la scena e scrive il codice TikZ che a sua volta verrà digerito da TeX.
Comunque sono un’iscritto al Guit e conosciuto sul forum come Azoun.
Saluti e Buona Pasqua anche a te.
Michele
Fantastico!
Anche Agostino si interessa a LuaTeX ed al tipo di applicazioni che consentirà . A mio avviso renderà molto più facile per l’utente finale utilizzare LaTeX rendendo di fatto elevatissima la capacità di elaborare dati e linguaggio.
Per Sketch ti ricordo solamente che LuaTeX incorpora il motore di disegno vettoriale METAPOST, anche se TiKz è un pacchetto eccezionale.
Ci sentiamo allora anche sul forum del GuIT, ciao.
Pingback: Numeri in lettere con Python « Robitex's Blog