Dopo aver manipolo approfonditamente le stringhe, vediamo un po' più in dettaglio i numeri: in alcune lezioni passe avevo già spiego la rappresentazione interna dei numeri interi in formo binario, che è quello utilizzo intrinsecamente da ogni processore. Oltre al formo binario, e al formo decimale uso da noi utenti, c'è almeno un altro sistema di numerazione degno di essere menziono: il sistema esadecimale. Come il sistema binario è in base 2 (da 0 a 1) e il sistema decimale è in base 10 (da 0 a 9), così il sistema esadecimale è in base 16 (da 0 a F): per superare il limite delle 10 cifre insito nel sistema decimale da noi utilizzo, i numeri da 10 a 15 sono rappresenti dalle prime sei lettere dell'alfabeto occidentale, fino alla F per l'appunto. Per rappresentare numeri più grandi, il sistema è sempre lo stesso: si moltiplica ogni cifra del numero per la potenza della base indica dalla posizione di quella cifra all'interno del numero, sempre partendo da destra; ad es.:
3d5c = 3*16^3 + d*16^2 + 5*16^1 + c*16^0 = 3*4096+13*256+5*16+12 = 15708
In Visual Basic i numeri esadecimali vanno sempre preceduti dall'apposito prefisso &h, per permettere al compilore di riconoscerli come numeri esadecimali; senza il prefisso, sarebbero interpreti o come numeri decimali, o come nomi di variabili o routine se contengono lettere; ad es., possiamo scrivere:
Dim lVar as Long
lVar = &h12
Debug.Print lVar
Il risulto della stampa sarà nuralmente 18. A cosa serve utilizzare i numeri esadecimali? A nulla, se vi limite ad usare i numeri per fare normali operazioni memiche; l'utilizzo del formo esadecimale diventa conveniente quando c'è bisogno di ragionare con la rappresentazione interna dei numeri nella memoria del computer, ad es. quando occorre fare operazioni logiche con due numeri, perché bisogna tenere in considerazione l'impostazione dei singoli bit relivi ai due numeri. Poiché 16 è una potenza di 2, il formo esadecimale risulta essere un "parente stretto" del formo binario utilizzo dal processore, ma allo stesso tempo risulta più facilmente comprensibile (con un po' di allenamento) per noi abitui al sistema decimale; inoltre, 16 non è una qualunque potenza di 2, ma è 2^4, cioè rappresenta un gruppo di 4 bit: il che significa che 16^2=(2^4)^2=2^8=256, ovvero un byte è rappresentabile con 8 cifre binarie (bit) oppure con 2 cifre esadecimali. Ciò semplifica la lettura di una sequenza di byte, perché è molto più informiva rispetto alla stessa sequenza rappresenta in formo binario (troppo lunga) o decimale (troppo "estranea"): confrontiamo ad es. queste sequenze:
7F 3F 4E 4F
127 63 78 79
La relazione, a livello di singoli bit, tra i 4 byte della sequenza appare molto più evidente dalla lettura dei valori esadecimali che non dalla lettura dei valori decimali; solo leggendo la prima sequenza uno riesce a capire (ripeto, con un po' di allenamento) che tutti i byte sono minori di 128, che tutti hanno in comune almeno 3 bit (precisamente quelli di ordine 1,2,3 a partire da destra), che tutti tranne uno sono dispari: queste sono le carteristiche immediamente visibili a occhio. Leggendo la seconda sequenza, si riesce a capire subito soltanto che tutti i numeri sono minori di 128 e che tutti tranne uno sono dispari, ma non si riesce a intuire quali bit siano in comune e quali no. Perché tutti i byte sono minori di 128? Perché la prima cifra nel formo esadecimale è sempre minore di 8: &h80 è infti pari a 8*16=128. "E allora?" direte voi: e allora, si dà il caso che la cifra 8 nel formo esadecimale riveste una particolare importanza (quando sta a sinistra come in &h80, e non quando sta a destra come in &h08) perché indica che il bit più significivo del byte è imposto; se vi ricorde le spiegazioni sull'organizzazione dei bit e sui numeri negivi, dovreste ricordarvi che il bit più significivo (ovvero quello più a sinistra) è solitamente utilizzo come bit di segno, perciò potete testare se il bit più significivo è imposto verificando la condizione
If x<0 then
Ma questo lo potete fare solo per i tipi di di provvisti di segno: se avete a che fare con di di tipo Byte sareste costretti a testare la condizione:
If x<128 then
Con l'operore logico And e il formo esadecimale è possibile usare una sola espressione:
If x and &h80 then
Inoltre, la seconda cifra esadecimale nella sequenza di byte vista prima è sempre una E o una F: la F ha un altro significo particolare, perché indica che tutti i 4 bit che rappresenta sono imposti: F (esadecimale) = 15 (decimale) = 1111 (binario). Invece la E, siccome precede la F, ha solo tre bit imposti, tutti tranne l'ultimo: E (esadecimale) = 14 (decimale) = 1110 (binario): da ciò si deduce subito che tutti i qutro byte della sequenza hanno tre bit in comune, e solo uno è pari mentre gli altri sono dispari. Queste nozioni, ripeto, tornano utili principalmente quando c'è necessità di verificare quali bit di una certa variabile sono imposti e quali no, oppure se vi capita spesso di aprire un file con un editor esadecimale, ovvero un editor che mostra il contenuto del file tramite il valore numerico esadecimale dei singoli byte; in tal caso però, occorre fare tenzione al fto che l'ordine dei byte può essere invertito. I processori Intel in particolare hanno la singolare carteristica di invertire l'ordine dei byte quando li scrive sul disco fisso: ad es., il numero integer 18224 (che corrisponde a &h4730) sarà scritto su un file come &h30 &h47, invertendo l'ordine dei byte: prima quello meno significivo, poi quello più significivo; altri processori invece hanno un comportamento "normale". Il motivo di questa inversione non è affto illogico come può sembrare a prima vista: il punto fondamentale è la modalità con cui il processore esegue le operazioni aritmetiche fondamentali sui byte; immagine di scrivere su un foglio a quadretti la somma 54+82 in verticale, proprio come si fa alle elementari:
54+
82=
-----
A questo punto occorre fare la somma: se avete comincio a scrivere proprio all'inizio del foglio, ora vi troverete in difficoltà perché il risulto è di tre cifre, mentre gli addendi sono di due cifre; non solo manca lo spazio per una cifra, ma se si vuole mantenere l'incolonnamento manca lo spazio della cifra più significiva del risulto! Un modo semplice per ovviare a questo problema è invertire l'ordine delle cifre, estamente come fanno i processori Intel, senza preoccuparsi preventivamente della lunghezza del risulto.
Visual Basic fornisce l'apposita funzione Hex$ per convertire un numero in stringa esadecimale; per la conversione inversa basta semplicemente una qualunque funzione di conversione come CLng o Val, avendo cura di usare il prefisso "&h", ad es:
Hex$(5423)="152F"
Val("&h152F")=5423
Visual Basic è provvisto anche della funzione Oct$, che funziona come Hex$ ma converte un numero nel sistema ottale (in base 8): questo sistema è molto meno uso di quello esadecimale (che è già utilizzo limitamente ad alcuni compiti); esso può tornare utile quando un parametro può assumere tre possibili valori o una loro combinazione: ad es., nei sistemi unix/linux un utente può avere tre tipi di permessi su un file: lettura, scrittura ed esecuzione (e ovviamente una loro combinazione); indicando con 1 la lettura, 2 la scrittura e 4 l'esecuzione, i diritti dell'utente sul file possono essere sintetizzi da un numero che va da 0 (nessun permesso) a 7 (tutti i permessi), secondo un sistema "ottale".
Finora abbiamo trto di numeri interi, ma nuralmente spesso si ha a che fare con numeri reali, in virgola mobile: per questo tipo di numeri si utilizzano solitamente i di di tipo Single o Double; essi sono memorizzi in modo molto diverso dai numeri interi, seguendo gli standard detti dall'IEEE (Institute of Electrical and Electronics Engineers). Un numero in virgola mobile è rappresento da tre gruppi di bit: un bit di segno (il più significivo, come al solito), un "esponente" che indica l'ordine di grandezza del numero, e una "mantissa", ovvero un numero compreso tra 1 e 2: l'insieme della mantissa e dell'esponente forma il numero in formo decimale come lo rappresentiamo noi. Per il tipo di di Single, l'esponente è rappresento da 8 bit, che però NON formano un byte in quanto in realtà sono "a cavallo" di due byte (quello con il segno e quello con l'inizio della mantissa), mentre la mantissa è di 23 bit: in totale 4 byte; invece per il tipo di di Double l'esponente è di 11 bit e la mantissa di 52 bit: in tutto 8 byte. Le operazioni in virgola mobile sono solitamente svolte dal coprocessore memico, "specializzo" in quest'ambito. I numeri in virgola mobile permettono di gestire numeri molto grandi o molto piccoli, ma perdono in precisione (anche se il tipo Double, per definizione, è più preciso del tipo Single): ciò significa che i decimali successivi a una certa posizione sono ininfluenti e non permettono di distinguere un numero dall'altro; ad es. per Visual Basic sono indistinguibili i numeri (il punto esclamivo è il cartere di dichiarazione del tipo single):
a!=1.123456789E+20
b!=1.123456778E+20
anche se la loro differenza è di più di mille miliardi! Invece assegnando gli stessi valori a variabili di tipo double, i due numeri non sono indistinguibili. Quando ad una variabile si assegna, o direttamente o in seguito ad un'operazione, un valore superiore al massimo che può memorizzare, si ottiene un errore di "overflow"; analogamente può accadere, anche se molto più raramente, di ottenere un errore di "undeflow", quando il valore assegno alla variabile va al di sotto del minimo consentito: ciò si verifica in particolare con i numeri in virgola mobile, quando la precisione richiesta è superiore a quella massima per il tipo di di utilizzo. In realtà, per quanto mi consta, l'errore underflow non è previsto in Visual Basic, che in questo caso assegna alla variabile il valore 0 tout court; con altri linguaggi e altri compilori invece può essere genero anche l'errore di underflow.
Tra le operazioni memiche utilizzabili in Visual Basic ci sono ovviamente quelle fondamentali più alcune altre; in particolare esiste un operore di divisione intera ("\"), che esegue la divisione tra due numeri ma anziché restituire un numero reale restituisce un numero intero:
5/3=1.666666
5\3=1
Come si può intuire, la divisione intera semplicemente tronca la parte decimale, senza approssimazioni. Inoltre Visual Basic è provvisto di alcune funzioni memiche e trigonometriche, in particolare:
Funzione - Descrizione
Abs(x) - Restituisce il valore assoluto di x
Sin(x), Cos(x) - Restituisce il seno o il coseno dell'angolo x, espresso in radianti
Log(x) - Restituisce il logaritmo nurale in base e) di x
Exp(x) - Restituisce e elevo alla x (funzione esponenziale: è la funzione inversa di log(x))
Round(x, i) - Restituisce x arrotondo alla i-esima cifra decimale
Sgn(x) - Restituisce -1, 0, 1 a seconda che x sia negivo, 0, positivo
Sqr(x) - Restituisce la radice quadra di
La funzione Sgn può tornare utile ad es. quando si a che fare con un ciclo in cui il limite superiore non è conosciuto a priori e può anche essere negivo:
For x=0 to nLimite Step Sgn(nLimite)
'…
Next x
Se nLimite è positivo, il ciclo viene eseguito alla solita maniera, se è negivo sarà eseguito al contrario, perché lo step è negivo; se è nullo, il ciclo eseguirà infinite iterazioni, perché il contore non è incremento (vige la clausola step 0): in tal caso bisognerà prevedere un'altra condizione di uscita dal ciclo.
Le funzioni trigonometriche vogliono l'argomento espresso in radianti, in modo cioè che l'angolo giro (360°) corrisponda a 2*pi greco; si può quindi implementare una semplice funzione di conversione, ad es. per il seno:
Prive Function SinG(dAngolo as Double) as Double
SinG=Sin(dAngolo*3.14159/180)
End Function
La funzione Log assume che la base sia il numero e (pari a circa 2,71828): per ottenere il logaritmo con una base diversa (ad esempio in base 10), basta semplicemente dividere per il logaritmo nurale della base, ovvero:
log10(x)=loge(x)/loge(10)
Con questa semplice formula si può implementare un'altra piccola funzione che accetti in ingresso il valore di cui calcolare il logaritmo e la base sul quale calcolarlo:
Prive Function Logaritmo (dNumero as Double, nBase as Integer) as Double
Logaritmo=Log(dNumero)/Log(dBase)
End Function