Dopo
aver manipolato approfonditamente le stringhe, vediamo un
po' più in dettaglio i numeri: in alcune lezioni passate
avevo già spiegato la rappresentazione interna dei
numeri interi in formato binario, che è quello utilizzato
intrinsecamente da ogni processore.
Oltre al formato binario, e al formato decimale usato da noi
utenti, c'è almeno un altro sistema di numerazione
degno di essere menzionato: 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 utilizzato, i numeri da 10 a 15 sono rappresentati
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 indicata 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 compilatore di riconoscerli
come numeri esadecimali; senza il prefisso, sarebbero interpretati
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
risultato della stampa sarà naturalmente 18. A cosa serve
utilizzare i numeri esadecimali? A nulla, se vi limitate ad
usare i numeri per fare normali operazioni matematiche; l'utilizzo
del formato 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 relativi ai due numeri.
Poiché 16 è una potenza di 2, il formato esadecimale
risulta essere un "parente stretto" del formato binario
utilizzato dal processore, ma allo stesso tempo risulta più
facilmente comprensibile (con un po' di allenamento) per noi
abituati 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:
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ù informativa rispetto alla stessa sequenza
rappresentata in formato binario (troppo lunga) o decimale (troppo
"estranea").
Confrontiamo ad es. queste sequenze:
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
caratteristiche immediatamente 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 formato esadecimale è
sempre minore di 8: &h80 è infatti pari a 8*16=128.
"E allora?" direte voi: e allora, si dà il
caso che la cifra 8 nel formato 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ù significativo del byte è impostato; se vi
ricordate le spiegazioni sull'organizzazione dei bit e sui numeri
negativi, dovreste ricordarvi che il bit più significativo
(ovvero quello più a sinistra) è solitamente utilizzato
come bit di segno, perciò potete testare se il bit più
significativo è impostato verificando la condizione
Con
l'operatore logico And e il formato esadecimale è possibile
usare una sola espressione:
Quando
l'utente seleziona la direzione di ricerca "tutto",
il cursore viene automaticamente impostato all'inizio del file:
pertanto non c'è più bisogno dell'istruzione lInizio=0
nella routine di ricerca:
Inoltre,
la seconda cifra esadecimale nella sequenza di byte vista prima
è sempre una E o una F: la F ha un altro significato
particolare, perché indica che tutti i 4 bit che rappresenta
sono impostati: F (esadecimale) = 15 (decimale) = 1111 (binario).
Invece la E, siccome precede la F, ha solo tre bit impostati,
tutti tranne l'ultimo: E (esadecimale) = 14 (decimale) = 1110
(binario): da ciò si deduce subito che tutti i quattro
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 impostati 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 attenzione al fatto che l'ordine dei byte può essere
invertito.
I processori Intel in particolare hanno la singolare caratteristica
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 significativo, poi quello
più significativo; altri processori invece hanno un comportamento
"normale".
Il motivo di questa inversione non è affatto illogico
come può sembrare a prima vista: il punto fondamentale
è la modalità con cui il processore esegue
le operazioni
aritmetiche fondamentali sui byte; immaginate di scrivere su
un foglio a quadretti la somma 54+82 in verticale, proprio come
si fa alle elementari:
A
questo punto occorre fare la somma: se avete cominciato a
scrivere proprio all'inizio del foglio, ora vi troverete in
difficoltà perché il risultato è 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ù significativa del risultato!
Un modo semplice per ovviare a questo problema è invertire
l'ordine delle cifre, esattamente come fanno i processori
Intel, senza preoccuparsi preventivamente della lunghezza
del risultato.
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 usato di quello esadecimale
(che è già utilizzato limitatamente 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 sintetizzati da un numero che va da 0
(nessun permesso) a 7 (tutti i permessi), secondo un sistema
"ottale".
Finora
abbiamo trattato di numeri interi, ma naturalmente spesso
si ha a che fare con numeri reali, in virgola mobile: per
questo tipo di numeri si utilizzano solitamente i dati di
tipo Single o Double; essi sono memorizzati in modo molto
diverso dai numeri interi, seguendo gli standard dettati dall'IEEE
(Institute of Electrical and Electronics Engineers). Un numero
in virgola mobile è rappresentato da tre gruppi di
bit: un bit di segno (il più significativo, 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 formato decimale come lo rappresentiamo noi.
Per il tipo di dati Single, l'esponente è rappresentato
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 dati 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 matematico,
"specializzato" 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 esclamativo è
il carattere 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 assegnato 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 dati utilizzato.
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 compilatori invece può essere generato anche
l'errore di underflow.
Tra le operazioni matematiche utilizzabili in Visual Basic
ci sono ovviamente quelle fondamentali più alcune altre;
in particolare esiste un operatore di divisione intera ("\"),
che esegue la divisione tra due numeri ma anziché restituire
un numero reale restituisce un numero intero:
Come
si può intuire, la divisione intera semplicemente tronca
la parte decimale, senza approssimazioni. Inoltre Visual Basic
è provvisto di alcune funzioni matematiche 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 naturale (in base e) di x |
Exp(x) |
Restituisce
e elevato alla x (funzione esponenziale: è
la funzione inversa di log(x)) |
Round(x,
i) |
Restituisce
x arrotondato alla i-esima cifra decimale |
Sgn(x) |
Restituisce
-1, 0, 1 a seconda che x sia negativo, 0, positivo |
Sqr(x) |
Restituisce
la radice quadrata di x |
La
funzione Sgn può tornare utile ad esempio quando
si a che fare con un ciclo in cui il limite superiore non
è conosciuto a priori e può anche essere negativo:
For
x=0 To nLimite Step
Sgn(nLimite)
'...
Next x |
Se
nLimite è positivo, il ciclo viene eseguito alla solita
maniera, se è negativo sarà eseguito al contrario,
perché lo step è negativo; se è nullo,
il ciclo eseguirà infinite iterazioni, perché
il contatore non è incrementato (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:
Private
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 naturale della base, ovvero:
log
10(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:
Private
Function
Logaritmo (dNumero As Double,
nBase As Integer) As Double
Logaritmo = Log(dNumero)/Log(dBase)
End Function |
|