Robitex's Blog

Ideas in the web

Numeri in lettere


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.

The compile session of the java program

The compile session of the java program

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.

10 risposte a “Numeri in lettere

  1. Michele 21/04/2011 alle 22:59

    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

    • robitex 22/04/2011 alle 09:06

      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.

  2. robitex 22/04/2011 alle 10:45

    Un quesito:
    il codice restituisce una traduzione non corretta per i numeri negativi, riuscite a spiegarne il motivo?
    Bye

  3. Michele 22/04/2011 alle 11:10

    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

    • robitex 22/04/2011 alle 11:35

      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.

  4. Pingback:Numeri in lettere con Python « Robitex's Blog

  5. Pingback:Numeri in lettere in Go « Robitex's Blog

  6. Pingback:Numeri in lettere in Falcon « Ok, panico

  7. Carmine 05/07/2016 alle 19:07

    Qualcuno si è mai posto il problema al contrario, ovvero trasformare le parole che descrivono un numero in numeri? Ovvero ‘duemila’ –> 2000 oppure duecentosettantaquattro –> 274

Lascia un commento

Inserisci i tuoi dati qui sotto o clicca su un'icona per effettuare l'accesso:

Logo WordPress.com

Stai commentando usando il tuo account WordPress.com. Chiudi sessione / Modifica )

Foto Twitter

Stai commentando usando il tuo account Twitter. Chiudi sessione / Modifica )

Foto di Facebook

Stai commentando usando il tuo account Facebook. Chiudi sessione / Modifica )

Google+ photo

Stai commentando usando il tuo account Google+. Chiudi sessione / Modifica )

Connessione a %s...

%d blogger cliccano Mi Piace per questo: