|
Anche questo è un argomento di grande importanza ed anche un po' critico, da sempre. Il problema è che i computer "parlano" con elementi interi in un sistema binario fatto di 0 e 1e in numeri con virgola o floating point hanno delle peculiarità concettuali difficili da riprodurre in un simile sistema. Infatti, tutto sommato, qualche particolarità c'è sempre ma possiamo per lo più ignorare questi aspetti. I numeri con virgola si presentano nel consueto formato, niente di nuovo x = 1.2 y = 0.34 vanno bene ma non z = .22 la notazione accettata è quindi solo x.y. Un'altra notazione accettata è quella esponenziale: x = 3.12345e3 equivale a 3.12345 * 10^3 quindi a 3123.45 Crystal ci propone due tipi di numeri con virgola:
che ci dà il seguente output:
Anche le operazioni di base non creano particolari problemi:
l'output è:
Quindi, la riga 3 ci indica che il tipo attribuito di default è float64. Se vogliamo forzare l'appartenenza ai float32 dobbiamo ricorrere al solito metodo per forzare l'attribuzione del tipo da noi scelto, ovvero, come nel caso degli interi, si usa un suffisso: x = 7.3_f32 tutto il resto del programma direi che non presenta difficoltà concettuali particolari, le operazioni sono quelle classiche viste nel paragrafo relativo agli interi. Vi sono alcune differenze, ad esempio non è possibile applicare tdiv per avere la parte intera della divisione. E' disponibile invece remainder che funziona come nel caso degli interi. Tuttavia potrete avere il quoziente di una divisione tra numeri con virgola usando un metodo proprio dei nimeri con virgola ovvero trunc, come dice il nome stesso, tronca un numero alla sua parte intera: x = 3.2 y = 1.5 puts (x / y).trunc che ci restituisce 2.0. Ovviamente può essere applicato anche sul singolo numero. Un altro sistema fa uso del metodo to_i che permette il passaggio da float a intero eliminando la parte decimale ma anche modificando del tutto la natura del numero, originariamente nato come float mentre il risultato è un intero a tutti gli effetti. Vediamo le differenze in questo breve codice:
il cui output è:
Ovvero un float e un intero. Nello scorso paragrafo abbiamo visto to_f che fa il passaggio inverso da intero verso float. Il passaggio inverso da intero verso float è molto semplice:
il cui output è:
se volete forzare il passaggio verso un float32 dovrete usare to_f32. provare per credere. Il passaggio da f64 a f32 è possibile con le medesime modalità ma attenzione alla perdita di informazione. Il passaggio inverso è possibile ma gli esiti potrebbero essere non sempre precisi per cui, personalmente, lo sconsiglio. L'esempio successivo vi illustra la cosa, analizzate con attenzione i risultati:
e il suo output:
La convivenza tra interi e float è abbastanza pacifica. Le 4 operazioni di base che coinvolgano un numero di un tipo e un numero dell'altro danno un risultato di tipo float anche se intero come valore. Ovvero, ad esempio 4.0 * 2 dà 8.0 e non 8. Un altro passaggio che potrebbe capitare, ad esempio importando i dati da e verso un file o un database, è quello della conversione da e verso il classico formato stringa. Il passaggio verso stringa è molto semplice grazie al metodo to_s: x = 2.333 y = x.to_s puts y + 'a' e vedrete che il carattere 'a' si aggiunge al vostro 2.333 convertito in sequenza di caratteri (tale è una stringa, come vedremo). Il passaggio inverso invece è un po' più delicato non tanto se convertite direttamente un numero quanto, come già visto nel caso degli interi, se gestite lo standard input: print "Inserisci un numero: " f1 = gets() f2 = (f1.to_s).to_f puts f2 + 1 Questo è il modo corretto, similmente a quanto visto per gli interi nel capitolo 2. E' il momento per vedere un po' di metodi applicabili sui numeri in virgola. Abbiamo già visto to_s, to_i, trunc, nel seguente esempio aggiungiamo altri elementi utili:
ceil - arrotonda all'intero superiore più vicino - risultato sarà 2 floor - arrotonda all'intero inferiore più vicino - risultato sarà 1 round - arrotonda secondo alcune regole che vedremo nell'esempio successivo - in questo caso il risultato è 1 modulo - resituisce il resto della divisione tra il numero su cui è applicato e quello indicato nella parentesi. Il risulato potrebbe non essere preciso ma solo una ottima approssimazione. Nell'esempio a me è saltato fuori 1.2999999999999998 invece di 1.3 Ovviamente, vi sono molti altri metodi applicabili, che trovate sul sito ufficiale. Quanto a round gli esempi successivi dovrebbero chiarire il suo comportamento: puts 1.2.round restituisce 1 puts 1.4.round restituisce 1 puts 1.41.round restituisce 1 puts 1.5.round restituisce 2 puts 1.9.round restituisce 2 puts 1.23456.round(3) arrotonda secondo le regole precedenti ammettendo 3 cifre decimali. Anche per i numeri con virgola può essere in qualche caso necessario spingersi un po' più in là con la profondità di calcolo ed in questo caso disponiamo di due tipi che fanno al caso nostro: BigDecimal: numeri con virgola aventi con precisione arbitraria BigFloat: numeri con virgola grandi a piacere Anche in questo caso, si tratta di tipi pesanti che possono impattare negativamente (in caso di uso intenso, si capisce, non nei nostri semplici test) le prestazioni della vostra applicazione. Un breve esempio: require "big" x = BigDecimal.new("1.23445459485485043850943859430009866") puts x + 1 Ne riparleremo nella sezione apposita dove approfondiremo anche il discorso dei BigInt e dei, finora non citati, BigRational. Vediamo per chiudere i soliti casi critici che ricalcano pari pari quelli visti per gli interi: puts 0.0 / 0.0 puts 1.0 / 0.0 puts -1.0 / 0.0 come risultato avremo rispettivamente Nan, Infinity e -Infinity mentre anche per questo tipo abbiamo il problema del resto per i dividendo negativi puts -7.0 % 3.0 puts -7.0.remainder(3.0) puts -7.0.modulo(3.0) col seguente output:
che ci ripresenta la solita ambiguità, almeno in apparenza, in realtà è proprio diversa la natura dell'operazione. Con questo, abbiamo concluso il nostro rapido excursus sui numeri in virgola ed il loro uso. Non vi resta che fare un po' di prove. |