Robitex's Blog

Ideas in the web

Archivi Mensili: aprile 2011

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.

Annunci

Potenze di somme


Scarica l’articolo in formato PDF per la stampa

L’elegante espressione

Stavo leggendo le prime pagine del libro di Lev Landau dedicato alla teoria dell’elasticità (il volume 7 del suo corso di Fisica pubblicato in Italia da Editori Riuniti), e mi sono imbattuto in un’espressione matematica interessante. Si tratta del quadrato di un trinomio espresso come somma di prodotti.

Sappiamo che l’espressione del quadrato della somma di tre termini è:

\displaystyle (x_1+x_2+x_3)^2= x_1^2+x_2^2+x_3^2+2x_1 x_2+2x_1 x_3+2x_2 x_3.

Nel libro di Lev Landau la potenza della somma è rapprensentata come:

\displaystyle (x_1+x_2+x_3)^2=\sum_{i=1}^3 \sum_{j=1}^3 x_i x_j.

La mia prima impressione è stata quella che qualcosa non tornasse perché quando nella sommatoria gli indici si incrociano ottengo i termini del doppio prodotto con un appropriato fattore 2 mentre i termini quadrati ottenuto con indici uguali assumevano tale fattore ingiustamente. In realtà l’espressione è perfettamente corretta perché i due indici i e j sono sempre unici nella combinazione, solo che quando sono diversi danno luogo a due termini uguali. Per esempio per la coppia 1,2 ottengo il termine x_1 x_2 che si somma con il termine x_2 x_1 quando gli indici diventano appunto 2, 1, mentre esiste una sola combinazione che genera il termine x_1^2, quando gli indici sono entrambe pari ad uno.

In generale, se n è il numero dei termini che sommati sono elevati al quadrato, il numero dei termini dello sviluppo risultante della doppia sommatoria è n^2 di cui n sono i termini quadratici e n^2-n sono i termini misti ma solo la metà di essi risultano distinti. In definitiva la doppia sommatoria produce gli (n^2+n)/2 termini dello sviluppo del quadrato. Nel nostro caso n vale 3 per cui lo sviluppo conta 6 termini.

Facile è dimostrare l’elegante espressione infatti il quadrato della somma si può scrivere come:

\displaystyle \left(\sum_i x_i\right)^2=\sum_i x_i \sum_j x_j,

dove abbiamo introdotto un secondo indice j in sostituzione del primo in uno dei due termini uguali del prodotto. A questo punto otteniamo la dimostrazione riscrivendo l’espressione precedente spostando il termine x_i all’interno della sommatoria con indice j essendo in effetti una costante nei riguardi di quest’ultima somma:

\displaystyle \sum_i x_i \sum_j x_j= \sum_i \sum_j x_i x_j.

Estensione

Il libro sull’elasticità utilizza la simbologia compatta detta notazione di Einstein, per la quale si ommettono nelle espressioni matematiche le sommatorie intendendo che termini con indici vanno sommati rispetto ad essi. L’espressione in forma compatta che abbiamo incontrato prima si scrive semplicemente come (x_i)^2=x_i x_j.

La regola si può estendere per potenze intere qualsiasi di un numero qualsiasi di termini. Per esempio il cubo del trinomio può essere scritto come una tripla sommatoria del prodotto ternario:

\displaystyle (x_1+x_2+x_3)^3=x_i x_j x_k.

Il numero di sommatorie ed il numero dei termini del prodotto è pari a quello dell’esponente della potenza, mentre il numero dei termini della somma compare come indice finale delle sommatorie (implicite) stesse.

Come ulteriore esempio, la quinta potenza del binomio sarà quindi espressa da:

\displaystyle (x_1+x_2)^5=x_i x_j x_k x_l x_m

Conclusione

A parte il fatto che l’argomento del post sembra essere interessante, vorrei concludere dicendo che le scienze applicate come la fisica o l’ingegneria sanno essere un continuo generatore di idee e di problemi per la matematica e, naturalmente, che vale anche l’opposto. Non si è quindi mai sicuri nel ritenere un’astrusa teoria matematica come perfettamente inutile o nel pensare che una nuova teoria fisica non richieda la generazione di nuovi risultati assolutamente inattesi per la scienza esatta.

%d blogger hanno fatto clic su Mi Piace per questo: