|
I primi elementi con cui ci imbattiamo in questo nostro percorso sono i numeri interi. Si tratta un tipo di dati ben noto e che anche in Crystal non presenta particolari sorprese. Questo linguaggio, non diversamente da molti altri, propone sostanzialmente i numeri interi in una classica suddivisione legata all'estensione della rappresentazione in bit e dalla presenza o meno del segno. Iniziando da quelli con segno abbiamo:
Crystal come sappiamo supporta l'inferenza di tipo e attribuisce ad un numero un tipo di default che è Int32: x = 8 puts typeof(x) restituisce Int32, appunto. Se vogliamo forzare l'appartenenza ad uno specifico tipo numerico dobbiamo ricorrere ad un suffisso: x = 8_i8 puts typeof(x) e in questo caso, appendendo il tipo desiderato al numero abbiamo obbligato il compilatore ad attribuire proprio quel tipo. Per specificare il tipo bisogna ricorrere ad una notazione abbreviata come nell'esempio i8 per gli Int8 o i64 per gli Int64 e u16 per Uint16. Ovviamente c'è sempre il rischio di incorrere in forzature errate: x = 300_i8 puts typeof(x) questo programma non compila e ci viene detto:
l'errore, evidenzia il fatto che il valore 300 non può stare nell'ambito del range degli i8, il che coincide con questo presentato nell'elenco visto all'inizio di questo capitolo. Il messaggio rilasciato dal compilatore mi sembra molto chiaro in merito. Forzare elementi in un ambito può essere rischioso come ci dimostra il seguente frammento: x = 120_i8 x = x + 200 mandare in esecuzione questo codice ci garantisce un errore:
Anche qui c'è una riga, la prima, che ci indica che qualcosa è andato storto, in particolare siamo andati in overflow in quanto 120 + 200 esonda dai valori accettabili per gli i8. Impareremo più avanti a gestire queste situazioni. Il problema dell'overflow è potenzialmente presente qualunque sia il tipo di interi che utilizzati, fossero pure gli UInt64, quindi valutate bene le grandezze che andrete a manipolare. Tenete presente che Crystal non prevede conversioni automatiche di tipo in caso di overflow per cui ... fate bene i vostri conti. Se vi può consolare, sappiate che sono problematiche insite in quasi tutti i linguaggi di programmazione, non è "colpa" di Crystal. Ora concentriamoci su cose più semplici, come le operazioni di base:
La riga 1 e la 2, confermano l'uso di = per assegnare valori alle variabili, come già visto. Dalla riga 3 alla 6 non dovrebbero esserci problemi, si tratta delle classiche operazioni aritmetiche di base, osservando l'output
notiamo che (finalmente) 7 diviso 3 fa 2,3 periodico, che è il risultato giusto di quella divisione e non 2 come in molti altri linguaggi. L'operatore % restituisce il resto della divisione mentre se volete la parte intera, il quoziente, Crystal vi offre il metodo tdiv, come alla riga 8. La riga 9 realizza l'elevamento a potenza mentre la 10 e la 11 ci indicano un comodo metodo per ricordarci i limiti dei vari tipi, laddove non ce li fossimo scordati. MAX e MIN sono due costanti definiti per tutti i tipi interi. Se usate numeri grandi potrete usare anche una notazione alternativa, ad esempio, 1000000 può essere scritto 1_000_000 che probabilmente è più leggibile. Crystal è accompagnato anche una libreria matematica specifica che aggiunge numerose funzioni e costanti già pronte per l'uso. Questa libreria si chiama Math ed è di uso decisamente immediato, basta richiamarla direttamente insieme alla funzione o alla costante che volete utilizzare: puts Math.sqrt(2) puts Math::PI puts Math.log10(100) puts Math.cos(90) la prima istruzione calcola la radice di 2, la seconda stampa il classico e immortale PiGreco, la terza calcola il logaritmo in base 10 di 100 e la quarta il coseno di 90. Avete apprezzato come una funzione (metodo) sia richiamata tramite l'operatore . (punto) mentre una costante usando :: (due doppi punti). La libreria Math contiene veramente molte funzioni, trigonometriche, logaritmiche, statistiche, pronte all'uso, sul sito ufficiale trovate tutto. Anche Crystal permette le consuete rappresentazioni binaria, ottale ed esadecimale. E' necessario riorrere ai prefissi consueti anche in altri linguaggi: 0b per il caso binario 0o per il caso ottale 0x per il caso esadecimale Vediamo un esempio che ci mostra la trasformazione diretta e inversa verso questi formati:
Abbiamo incontrato in questo esempio un interessante metodo, ovvero to_s che converte un intero in stringa, nel paragrafo 2 avevamo visto to_i che trasforma da stringa a intero e che rivedremo quando parleremo appunto delle stringhe. Lo vediamo all'opera alla riga 7 dalla quale notiamo che questo metodo vale quindi anche per riversare da una base diversa da 10 in un decimale. Questo metodo non riguarda gli interi direttamente ma è comunemente usato per convertire verso il mondo degli interi.Queste conversioni possono essere molto utili. Tra l'altro, è possibile convertire anche in altre basi non standard, come 4 o 13 fino a 36 (10 numeri + 26 lettere) se proprio vi servisse. Anzi, sbirciando il codice sorgente si nota questo: raise ArgumentError.new("Invalid base #{base}") unless 2 <= base <= 36 || base == 62 raise ArgumentError.new("upcase must be false for base 62") if upcase && base == 62 Quindi le basi accettate sono quelle da 2 a 36 e poi 62 (10 numeri + 26 lettere minuscole + 26 lettere maiuscole) per quest'ultima base però non potete usare le lettere maiuscole nell'indicazione dell'elemento da convertire. Quando avremo visto gli operatori logici quel codice sarà più chiaro. Avrete già notato che abbiamo parlato più volte di "metodi" relativamente ai numeri. Non è un caso, ovviamente. Gli interi essendo oggetti essi stessi godono di parecchi metodi per la loro manipolazione, alcuni dei quali molto utili e che troveremo più avanti. Ad esempio times che come vedremo quando studieremo il controllo di flusso, permette di iterare il numero divolte indicato dall'intero intero.times è la semplice sintassi e vedremo anche i compagni upto, downto, step nello stesso ambito. Presento a questo punto una carrellata, assolutamente non esaustiva ma indicativa del modo d'uso di questi metodi che troverete insieme ad altri sempre sul sito ufficiale al quale vi consiglio sempre di dare un'occhiata: chr - permette da un intero di passare al carattere corrispondente: 97.chr corrisponde al carattere 'a' abs - restituisce il valore assoluto -23.abs equivale a 23 divisible_by?(numero) - ci indica tramite un valore boolano true o false se un certo numero è divisibile per un altro puts 2024.divisble_by(7) even - ci dice se un numero è pari puts 2024.even? gcd - restituisce il massimo comun divisore di numeri: puts 138.gcd(27) lcm - restituisce il minimo comune multiplo puts 24.lcm(5) odd? - ci dice se un numero è dispari puts 5.odd? succ - restituisce il numero successivo al quello indicato puts 7.succ to_f - cambia il tipo di numero da intero a float64 ( i numeri con virgola saranno protagonisti del prossimo paragrafo) x = 1 x = x.to_f a questo punto x è float64 e la sua rappresentazione è 1.0 e non più solo 1 to_s già visto che converte da numero a stringa, si tratta di un metodo piuttosto importante. 123.to_s trasforma da numero 123 a stringa "123" (delle stringhe parleremo più avanti). L'esempio successivo riassume un po' l'uso di questi metodi da un punto di vista pratico:
Ricordiamo ancora che l'accesso ai metodi avviene tramite l'operatore . (punto). Un ultimo step relativo ai numeri interi porta un nome "pesante": BigInt. Questi, come il nome stesso fa intuire, rappresentano semplicemente interi arbitrariamente grandi, quindi l'unica limitazione è quella imposta dall'hardware su cui il software viene eseguito. Questo tipo di dati è un po' più laborioso, è ad esempio è necessario installare il "development package for libgmp" cosa che si fa in paio di istruzioni. Fatto questo vediamo un piccolo esempio:
La prima riga è interessante ed introduce la keyword, require, che richiama una libreria esterna affinchè divenga utilizzabile all'interno del programma. La ritroveremo molto spesso e quanto più si faranno complessi i vostri programmi tanto più la dovrete utilizzare. La definizione del BigInt fa uso della keyword new che sarà più chiara quando parleremo delle classi, si tratta in pratica di una istanziazione. Vi lascio il piacere di osservare l'output del programma precedente. Come detto non esiste possibilità di utilizzo automatico o meglio conversione verso questo tipo in caso di overflow, è possibile affrontare questa situazione ma usando tecniche piuttosto avanzate che non è il caso di trattare a questo punto. I BigInt sono grandi e grossi e decisamente pesanti a livello computazionale. Li vedremo all'opera ma, in generale, se non sono necessari a tutti i costi potete farne a meno per non penalizzare dal punto di vista prestazionale le vostre applicazioni. Per chiudere vediamo alcuni casi interessanti numero /0 restituisce Infinity, ovvero, infinito come è giusto. Infinty non è un numero -numero/0 restituisce invece -Infinity, infinito negativo. 0/0 invece esibisce come risultato NaN cioè "Not a Number", anch'esso, lo dice il nome, non è un numero. % e remainder. Attenzione alla ben nota differenza tra "modulo" e resto. In pratica in Crystal, come in Ruby, come in altri linguaggi esiste l'operatore di resto della divisione e una funzione apposita di calcolo del resto stesso, che in Crystal si chiama remainder. Per valori positivi non c'è differenza ma per quelli negativi le cose possono cambiare, come dall'esempio seguente: puts (-7 % 3) puts -7.remainder(3) il risultato è 2 nel primo caso e -1 nel secondo. In breve, nel primo caso abbiamo -7 + 3 * 3 che fa appunto 2 nel secondo -7 + 3 * 2 che fa -1. Attenzione quindi. Su Internet trovate tutte le spiegazioni del caso che esulano da questa trattazione. Direi che per lavorare sugli interi ci sono abbastanza spunti. La cosa non finisce del tutto qui in quanto nel prossimo paragrafo affronteremo i numeri con virgola e vedremo le interazioni e la coesistenza tra queste due tipologie. |