Newsgroup: it.comp.lang.c++
Titolo: Variabili flot-double in memoria

Ciao, su questo NG ho letto un interessante "battibecco" riguardo al
risulatato di una sottrazione ( se non ricordo male) in floatting.
Quello che mi sono chiesto al dila' della precisione del risultato e' stato:
ma come viene definita in memoria una variabile float o double????
Cioe' mi spiego meglio ad es.  12,3456 come viene 'allocata' in memoria ??
come faccio conoscendo l'indirizzo a sapere com'e' strutturata la memoria
per contenere tale variabile???
Spero di non fare ridere nessuno.... ma prima non mi ero mai posto questo
problema.
Grazie a coloro che saranno in grado di spiegarmelo.
Ciao.

per contenere tale variabile???
I numeri i virgola mobile usano il formato IEEE.
I valori a singola precisione (float) consistiono di 4 bytes:
un bit per il segno ,
8 bit rappresentanti l'esponente binario in eccesso 127,
23 bit per la mantissa mantissa.
La mantissa rappresenta un numero compreso tra 1 e 2 (hidden bit).
esempio    float -0.125
1            01111100  00000000000000000000000
segno meno   124       0
(-1) * 2 ^ (-127 + 124) * [1 + 0 * 2^(-1) + 0* 2^(-4) + eccetera] = -1/8
per i double vale lo stesso ragionamento (con 8 bytes), solo che:
11 bits per l'esponente, 52 bits per la mantissa.

 Cioe' mi spiego meglio ad es.  12,3456 come viene 'allocata' in memoria ??
Normalmente viene utilizzato il formato IEEE per i numeri floating
point, un formato che richiede una mantissa ed un esponente o, come
diceva Alex tempo fa, un esponendo e un esponente (è così, Alex?). I
bytes utilizzati dipendono ovviamente dalla precisione adottata e cioè
dalle cifre significative garantite.
EMG

problema.
Quello che succede e` la seguente:
1) viene calcolato la rappresentazione in base (2 nel caso dei pc) che viene
normalizzata. In sostanza 0.3 in base 2 sarebbe
0.010011001100... che normalizzata diviene 2^1*(0.10011001100....).
2) il valore cosi` ottenuto viene memorizzato in termini di caratteristica (l'esponente
di 2^1 (cioe` 1) e di un certo numero di cifre della mantissa (le cifre che vengono memorizzate
dipendono anche dal fatto che si usi il troncamento o l'arrotondamento).
3) il valore 0 viene memorizzato con caratteristica e mantissa nulle. 
Questo tipo di rappresentazione permette di mantenere costante l'errore relativo
che si commette nell'approssimare un valore reale con uno di macchina (salvo
in caso di underflow in cui l'errore e` del 100% o nel caso di overflow in cui
l'errore non e` predicabile).
Nei pc i valori in virgola mobile sono rappresentati cosi` (la base e` sempre 2):
float (32 bit)
  Il bit piu` significativo (il 31) e` il segno
  i successivi 9 bit (dal 22 al 30) per la caratteristica (che se non erro e` rappresentata
            in traslazione, ovvero i valori che puo` assumere sono memorizzati in modo che 0
            rappresenti in realta` il valore minimo (-128 o -127?)
 i restanti 21 bit memorizzano 22 bit della mantissa (nella rappresentazione normalizzata
           la prima cifra della rappresentazione e` non nulla e quindi in base 2 vale 1 che e`
           memorizzato implicitamente)
double (64 bit)
   bit 63 = segno
   12 bit (51-62) per la caratteristica in traslazione
   i restanti 51 bit memorizzano 52 cifre (la prima e` sempre implicita)
long double (80 bit)
   bit 79 = segno
   15 bit (64-78) per la caratteristica in traslazione
   64 bit per una mantissa di 64 cifre (questa volta la prima cifra e` esplicitata
                anche se vale sempre 1 (tranne che per lo 0 ovviamente)
Riassumendo:
 
S = segno (sempre 1 bit),
E = esponente o caratteristica
M = mantissa
1 = bit implicito posto a 1
A sinistra i bit + significativi
float:
                               1
    [ S | E di 9 bit | M di 21 bit ]
double:
                                1
   [ S | E di 12 bit | M di 51 bit ]
long double:
  [S | E di 15 bit | M di 64 bit (1 bit esplicito) ]    
Spero di essere stato chiaro,
Paolo
P.s.
   Non saprei dirti se sui pc sia utilizzato l'arrotondamento o il troncamento).
----------------------------------------------------------
  Paolo Marotta
  Computer Science, University of Pisa
  home:   
http://www.cli.di.unipi.it/~marotta
  e-mail: 
mar...@cli.di.unipi.it
 
----------------------------------------------------------

per contenere tale variabile???
Dipende tutto dal compilatore, comunque c'e` uno standard
originalmente emesso dalla IEEE (Institute for Electric and
Electronic Engineers) e piu` recentemente adottato da ISO,
che si applica con qualche variazione su praticamente tutte
le macchine attuali.
Prendiamo, ad esempio, double.  La sua rappresentazione
standard ISO richiede 8 byte sulle macchine attuali.
Il bit piu` significativo e` il segno: 1 per numeri negativi,
0 per numeri positivi.
Segue poi lo "esponente", di 11 bit.  Questi 11 bit sono
visti come un intero senza segno nel range 0-2047;
togliendo a questo 1023, il "bias" previsto dallo standard,
ottieni un intero con segno, da -1023 a +1024 inclusi.
Questo intero va preso come esponente intero della 
base 2 in una espressione della forma:
    numero = pow(2,esponente) * significante;
dove il "significante", sempre secondo lo standard, e`
un numero maggiore o eguale ad 1 e strettamente
minore di 2.  Vi sono, naturalmente, vari casi speciali
che ti risparmio (zeri, infinita`, not-a-number's, numeri
in rappresentazione denormalizzata, eccetera).  Da
notare che il significante era _erroneamente_ chiamato
"mantissa" in una vecchia versione dello standard IEEE
(la mantissa, in realta`, ha a che vedere con i logaritmi,
che qui non sono in gioco); lo standard attuale ha per
fortuna corretto questo errore di denominazione.
Il significante, dunque, puo` essere visto come un
numero intero di N bit diviso per l'appropriata potenza
di due; questo denominatore e` implicito e quindi non
viene memorizzato, e neppure viene memorizzato il
bit piu` significativo del numeratore del significante,
visto che "sappiamo" che esso vale sempre esattamente
1 (con le eccezioni gia` sopra accennate).
In un "double" secondo lo standard, il significante e`
di 53 bit, di cui ne vengono dunque memorizzati solo
52.
    1 (segno) + 11 (esponente) + 52 (significante) = 64
il che ci conferma che un "double" occupa 8 byte (cioe`
64 bit).
Se vai a frugare in memoria per capire meglio questi
formati, usando, ad esempio, reinterpret_cast, ovvero
union fra un double e un array di 8 unsigned char,
attento a un dettaglio importante: lo standard non
precisa se i byte piu` significativi vadano memorizzati
agli indirizzi di memoria piu` bassi o piu` alti.  Delle
macchine attuali, le Intel (e imitatori) e le CPU Alpha
sono "little-endian", cioe` memorizzano i byte meno
significativi agli indirizzi bassi; le altre sono praticamente
tutte big-endian (byte PIU` significativi agli indirizzi
bassi).  Un consiglio per questo esercizio, se ti
interessa farlo: guarda prima gli equivalenti hex
di varie potenze intere di due (0.25, 0.5, 1, 2, 4...),
cosi` capisci come variano i bit dell'esponente; poi,
a esponente fissato, osserva vari numeri corrispondenti 
a frazioni con denominatore potenza intera di due,
ad esempio 1.5 (3/2), 1.25 (5/4), 1.75 (7/4), e cosi`
via, cosi` vedrai cambiare il significante in modi ancora
semplici e comprensibili.
Alex

 diceva Alex tempo fa, un esponendo e un esponente (è così, Alex?). I
"Significante" e` il termine usato dallo standard attuale in sostituzione
del precedente, e scorretto, "Mantissa".
Alex

 "mantissa"
Acc! Significante, non esponendo... ho detto una cazzata...
EMG

in rappresentazione denormalizzata, eccetera).  Da
ALTRO SNIP
Riguardo all'esponente: come è rappresentato? In complemento a due o in
forma polarizzata? Il tipo che faceva lezione non ci ha dato modo di
capirlo...

forma polarizzata? Il tipo che faceva lezione non ci ha dato modo di
Come ho appunto scritto: l'esponente e` visto come
intero senza segno a cui togliere un "bias" (credo che
il termine italiano classico sia in effetti quello di "forma
polarizzata", anche se non l'ho mai visto usare).
Alex

Direttamente dalle dispense che distribuisco ai miei studenti:
STANDARD IEEE 754 PER I NUMERI IN VIRGOLA MOBILE
RAPPRESENTAZIONI IN ECCESSO 2N-1
Prima di introdurre i dettami dello standard che dal 1980 disciplina la
convenzione di rappresentazione dei numeri in virgola mobile, introduciamo
un secondo tipo di rappresentazione dei numeri negativi in formato binario.
Dato un contenitore a m bit, si definisce rappresentazione di numeri
negativi in eccesso 2^(m-1) la rappresentazione che si ottiene quando, dato
un qualsiasi numero con segno s in notazione decimale ad esempio, si
rappresenta quel numero in binario con la forma binaria standard di: s +
2^(m-1)
ESEMPIO: RAPPRESENTAZIONI IN ECCESSO 8
Se si parla di eccesso 8 significa che il contenitore vale 4 (infatti
2^(4-1) = 8).
Iniziamo la rappresentazione a partire da zero:
 0, allora si considera  0 + 8 = 8, la cui rappresentazione binaria è 1000,
quindi  0 -> 1000
+1, allora si considera +1 + 8 = 9, la cui rappresentazione binaria è 1001,
quindi +1 -> 1001
+2, allora si considera +2 + 8 =10, la cui rappresentazione binaria è 1010,
quindi +2 -> 1010
...
+7, allora si considera +7 + 8 =15, la cui rappresentazione binaria è 1111,
quindi +7 -> 1111
Evidentemente non è possibile rappresentare +8, dato che +8+8=16 che non è
rappresentabile su 4 bit.
Si passa ai numeri negativi, e quindi si inizia con meno uno:
-1, allora si considera -1 + 8 = 7, la cui rappresentazione binaria è 0111,
quindi -1 -> 0111
-2, allora si considera -2 + 8 = 6, la cui rappresentazione binaria è 0110,
quindi -2 -> 0110
...
-8, allora si considera -8 + 8 = 0, la cui rappresentazione binaria è 0000,
quindi -8 -> 0000
Si noti come la rappresentazione eccesso 2^(m-1) è identica alla
rappresentazione in complemento due se si invertono i bit di segno.
STANDARD IEEE 754
Il documento IEEE 754 prima di tutto classifica le rappresentazioni
rispetto alla grandezza dei contenitori e quindi prevede:
- rappresentazione con precisione semplice a 32 bit
- rappresentazione con precisione doppia a 64 bit
- rappresentazione con precisione estesa a 80 bit
Naturalmente la rappresentazione dell’IEEE tratta numeri reali in virgola
mobile normalizzata utilizzando la convenzione del bit nascosto della
mantissa (primo bit della mantissa considerato di default a 1 e quindi non
riportato nella rappresentazione), la quale, non essendo completa, viene
chiamata significante. 
Inoltre la normalizzazione in base 2 adottata prevede sempre una cifra
intera a 1 binario (cioè le normalizzazioni binarie adottate sono della
forma 1,bbbbb.... invece che della forma standard 0,bbbbb...); pertanto il
bit nascosto non sarà la prima cifra dopo la virgola ma il bit a 1 sempre
presente nella parte intera.
Esaminando lo standard in precisione semplice, la disposizione degli
elementi all’interno dei 32 bit è la seguente:
 1(Segno)
 8(Esponente)
23(Significante)
Esaminando lo standard in precisione doppia, la disposizione degli elementi
all’interno dei 64 bit è la seguente:
 1(Segno)
11(Esponente)
52(Significante)
 
In precisione estesa i tre campi S,E,M valgono 1,15 e 64.
Le convenzioni utilizzate sono le seguenti:
· il bit di segno, che indica il segno globale del numero in virgola
mobile, è 0 per i numeri positivi e 1 per quelli negativi;
· l’esponente è espresso in eccesso 127 (precisione semplice) o in eccesso
1023 (precisione doppia)
· il significante è quasi sempre in virgola mobile normalizzata con bit
nascosto.
· eccezioni:
i. se l’esponente vale 0 e il significante vale 0, allora il numero
complessivo vale 0 a prescindere dal bit di segno;
ii. se l’esponente vale 0 e il significante è <> da 0, il numero
complessivo è denormalizzato;
iii. se l’esponente ha tutti i bit a 1, cioè è il massimo valore positivo
concesso in eccesso 128 o 1024, e il significante vale 0, il numero
complessivo vale infinito;
iv. se l’esponente ha tutti i bit a 1, cioè è il massimo valore positivo
concesso in eccesso 128 o 1024, e il significante è ¹0, il numero
complessivo è un NAN (Not a Number) cioè ad esempio infinito diviso
infinito.
Va da sè che un numero in virgola mobile normalizzata non potrà avere
esponente con tutti zeri o con tutti uno. Infatti si usa l’eccesso 127
(1023) e non l’eccesso 128 (1024) per evitare le due configurazioni di
esponente con tutti i bit uguali.
 
Un numero denormalizzato è tale quando l’esponente vale 0 e il significante
è <> 0; in questo caso si da come convenzione che l’esponente vale 2^(-127)
(o 2^(-1023)) e il significante ridiventa una mantissa non essendoci più il
bit nascosto. 
In questo caso si ottengono rappresentazioni fino a 2^(-150) (o 2^(-1075))
che allontanano l’underflow.
ESEMPIO: RAPPRESENTAZIONI IN PRECISIONE SEMPLICE IEEE 754
Vediamo la rappresentazione del numero 1,5 in precisione semplice.
Trasformato in binario si ottiene: (1,1)B.
Normalizzato: (1,1*10^0)B  (attenzione: 10B = 2D !)
Mantissa: 11000000
Significante: 10000000 (tolto il primo bit)
Esponente: (0)B = (0)D, quindi in eccesso 127: (0+127)D = (127)D =
(01111111)B
Segno: 0
(00111111110000000000000000000000)B IEEE754 = (1,5)D
(3   F   C   0   0   0   0   0   )H IEEE754 = (1,5)D