Newsgroup: it.comp.lang.visual-basic
Titolo: Numeri a virgola mobile: un altro caso

Ok, tenetevi perchè questa è forte.
A trovare questa in una query con SUM() su 200 record è stato, devo dirlo,
faticoso.
Ma a quanto pare riprodurre il problema è molto più semplice (purtroppo) di
quanto si pensi.
Occhio perchè la mano è più veloce dell'occhio e il trucco c'è ma NON SI
VEDE!
Prendete la finestra di debug e fate
?(1#-1.3#)
Fatto?
Ora questo
? Round(1#-1.3#, 3)
Ok? notato niente di strano?
Bene.
Adesso questo
? (1#-1.3#) - Round(1#-1.3#, 3)
Fantastico.
Ora ho scoperto che anche le operazioni di segno danno problemi.
Inoltre sono molto più subdole perchè come si è visto, il primo risultato
sembra corretto, ma in realtà contiene un bell'errore.
Controprova
? -0.3# - Round(1#-1.3#, 3)
Ora, a parte il fatto che viene da chiedersi come sia possibile usare questo
metodo di calcolo in programmi che non siano l'accademico calcolo dell'area
di un triangolo...
Qualcuno conosce ho si è già fatto un programma che dato un numero double ne
restituisce la rappresentazione binaria?
Nel senso proprio di sapere come sono memorizzati i bit nella variabile,
perchè a questo punto sono curioso e mi voglio fare a mano questi calcoli,
perchè devo far capire ai miei colleghi che se io metto a magazzino 10kg di
merce, e vendo 10kg, potrei avere ancora della rimanenza!
Poi posso capire se faccio moltiplicazioni e divisioni con numeri sballati,
ma 1 - 1.3 non mi può fare altro che -0.3!
So anche che sono stati postati più volte pagine di documentazione sulla
rappresentazione dei numeri IEEE.
Qualcuno le ha sottomano?
Ciao
--
Math Parser : 
http://www.neodatatype.net
Sito Comune : 
http://www.it-lang-vb.net

 Qualcuno le ha sottomano?
Ho appena dato un esame propro su questo (non solo, ovviamente), chiedi
e ti sarà spiegato :)
Ciao.
-- 
BSD is for UNIX lovers -- linux is for Windows haters.

 Nel senso proprio di sapere come sono memorizzati i bit nella variabile,
Proprio oggi pomeriggio m'è venuto il pallino di capire come vengano 
memorizzati i double in una variabile.
Allora ho fatto una CopyMem verso un array di 8 byte.
Ecco il risultato:
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" 
(Destination As Any, Source As Any, ByVal Length As Long)
Dim Dest() As Byte, Source As Double
Private Sub Form_Load()
Dim L As Byte
     L = 8
     ReDim Dest(1 To L) As Byte
     Source = 1#
     CopyMemory Dest(1), Source, L
     Print Binary(L)
End Sub
Private Function Binary(ByVal nByte As Byte) As String
Dim I As Byte, J As Byte
Dim S As String
     For I = 1 To nByte
         For J = 1 To 8
             S = (Dest(I) And 1) & S
             Dest(I) = Dest(I) \ 2
         Next J
     Next I
     Binary = S
End Function
L'ho provato con Source as Long e (quindi) L = 4 e ottengo la 
visualizzazione corretta del numero binario.
Ora non so cosa accada con 8 byte di sorgente, se magari vengono 
swappate ulteriormente le DWORD a coppia... boh?
Comunque basandomi sul LONG ottengo un risultato corretto...
-- 
Rivolterò il mondo per trovarlo, e anche se andasse in Indocina uno dei 
nostri starà nascosto in una ciotola di riso pronto a sparargli nel culo.
(Pulp Fiction)

 sopra, però 0.3 non è possibile scriverlo in modo esatto...
Ho provato a vedere 0.3 come veniva rappresentato (con un Single, ma
cambia poco tranne la precisione) ed in realtà viene rappresentato
0.30000001192092896


 ho letto Double ma ho pensato Single...
L'errore (enorme) l'ho commesso ora... 
Le modifiche scritte nel secondo messaggio sono da ignorare perché
assolutamente assurde...


 Comunque basandomi sul LONG ottengo un risultato corretto...
Il risultato è corretto anche con i Double.


 assolutamente assurde...
Vabbeh, deciditi :P
Comunque, quello che piů non capisco č perchč se il valore č
0.30000001192092896 debba essere visualizzato 0.3.
Perlomeno che sia chiaro dove sta il problema!
Poi magari il programmino me lo trascrivi in vb :)



Ok, adesso la domanda è
0.3 diventa
0011111111010011001100110011001100110011001100110011001100110011
Come faccio a capire che non è rappresentato in modo corretto?
E come sono rappresentati in genere i floating point?
Mi lascia perplesso ad es. il valore 1:
0011111111110000000000000000000000000000000000000000000000000000


 
L'ho consigliato almeno una decina di volte ... :-)
http://www.library.cornell.edu/nr/bookcpdf/c1-3.pdf
Procurati qualcosa per far passare il mal di testa.
ciao
	maxx
-- 
NOSPAM: Rimuovere i trattini dallo username!
Cut hyphens from my username!

 Poi magari il programmino me lo trascrivi in vb :)
Te l'hanno già scritto...
Comunque ti rispondoo in un altro messaggio sul formato, sperando 
di essere abbastanza chiaro (dammi solo l tempo di scrivere).


 0011111111110000000000000000000000000000000000000000000000000000
I 64 bit della rappresentazione binaria in virgola mobile IEEE 754 sono
così sufddivisi:
il bit più significativo (più a destra) è il bit di segno (S). I 
successivi undici bit sono l'esponente (E). I rimanenti cinquantadue bit 
sono la  mantissa (M).
Il numero è uguale a M * 2^E. Se S = 0 il numero è positivo, se S = 1 il 
numero è negativo.
Vediamo come sono codificati E ed M:
E rappresenta un numero con segno ed è codificato in eccesso 1023.
Le due codifiche 00000000000 e 11111111111 sono però riservate per usi 
particolari che vediamo in seguito.
Quindi le codifiche normali di E vanno da 00000000001 che rappresenta il 
numero -1022 (1 - 1023) a 11111111110 che rappresenta 1023 (2046 - 1023).
Esempi:
Nel caso di 0.3 E = 01111111101 che è 1021 - 1023 = -2
Nel caso di 1   E = 01111111111 che è 1023 - 1023 = 0
Per M il discorso è un po' più complicato. M è un numero rappresentato
in forma "normalizzata".
Qualche esempio per capire:
5/4 lo posso scrivere con M = 5 ed E = -2 (5/4 = 5*2^-2), ma anche con M
= 10 ed E = -3, ma anche con M = 20 ed E = -4, ecc...
Per evitare questa ambiguità che sarebbe decisamente problematica si
pone un vincolo (normalizzazione): la mantissa *deve* essere nella forma
1.xxx cioé la prima cifra a destra deve essere un 1 e la virgola è
posizionata subito dopo l'uno. Dato che la prima cifra a destra è sempre
un 1 (per il vincolo di normalizzazione) non ha senso sprecare un bit
per rappresentarla. Quindi l'uno è reso implicito.
Tornando agli esempi di cui sopra (tra parentesi l'uno implicito):
per 0.3 M = (1).0011001100110011001100110011001100110011001100110011
e per 1 M = (1).0000000000000000000000000000000000000000000000000000 
Quindi 1 è rappresentato come 1(M) * 2^0(E).
0.3 invece è 1.001100....ecc.... *2^-2 il che equivale a spostare la
virgola verso sinistra di 2 posizioni, quindi:
0.0100110011..ecc... = 2^-2 + 2^-5 + 2^-6 + 2^-9 + 2^-10 + ...ecc...
Se provi a fare il calcolo vedrai che non è esattamente 0.3.
Torniamo velocemente alle due codifiche di E lascate in sospeso.
E tutto a zero significa che la mantissa non è in forma normalizzata. La
virgola è posizionata prima del primo bit della mantissa ma *non* c'è
nessun uno implicito. L'esponente è assunto essere -1022. In questo modo
è inoltre rappresentabile lo zero (tutti i 64 bit a zero) che non
sarebbe codificabile in forma normalizzata.
E tutto ad 1 può assumere tre diversi significati:
se M  = 0 ed S = 0 indica + infinito        (ottenuto p.e. da 5/0)
se M  = 0 ed S = 1 indica - infinito        (   "     p.e. da -5/0)
se M != 0          indica NaN (Not a Number)(   "     p.e. da 0/0)
Direi che è tutto. Spero di essere stato sufficientemente chiaro.


 decimale (3 e 7 non sono fattori di 10).
Ripensandoci c'è qualcosa che continua a non quadrarmi.
Guarda qui:
(-0.3#) - Round(-0.3#, 3)
e
(1#-1.3#) - Round(1#-1.3#, 3)
dovrebbero essere equivalenti, dato che mi dici che -0.3 è comunque 
irrappresentabile.
Inoltre, anche li stessi
(1#-1.3#)
e
Round(1#-1.3#, 3)
Dovrebbero equivalere essendo entrambi double che valgono -0.3, e tutti 
sappiamo che -0.3 arrotondato alla 3a cifra fa ancora -0.3.
Perchè questa "doppia vita" di questo valore?
Ciao

 
Noto con dispiacere che non hai letto il link che ti ho consigliato. :-)
"Arithmetic among numbers in floating-point representation is not exact, 
even if the operands happen to be exactly represented (i.e., have exact 
values in the form of equation 1.3.1). For example, two floating numbers 
are added by first right-shifting (dividing by two) the mantissa of the 
smaller (in magnitude) one, simultaneously increasing its exponent, 
until the two operands have the same exponent. Low-order (least 
significant) bits of the smaller operand are lost by this shifting. If 
the two operands differ too greatly in magnitude, then the smaller 
operand is effectively replaced by zero, since it is right-shifted to 
oblivion."
Negli esempi che tu fai in alcuni casi c'e' una sola operazione 
aritmetica, in altri, quelli in cui si ottiene un risultato diverso da 
zero, piu' operazioni che evidentemente fanno perdere di precisione.
Lo si e' gia' detto anche in altri thread che con i floating-point non 
esiste lo zero, ma 2 numeri sono uguali a meno di un epsilon che dipende 
   da vari fattori (vedi l'articolo).
Leggi anche questo:
http://www.library.cornell.edu/nr/bookcpdf/c20-1.pdf


 zero, piu' operazioni che evidentemente fanno perdere di precisione.
Io sarò tardo, ma mi pare quindi di capire che lo stesso numero può 
essere rappresentato in modi diversi (come sequenza di bit) e quindi il 
-1.3 che esce da Round è diverso da -1.3 puro.
Mi sembra un'idiozia ma ne prendo atto.
La soluzione quindi è solo quella di fare un confronto con un numero 
sufficientemente piccolo?
Ciao


Round non è una funzione lineare.
Le rappresentazioni in virgola mobile hanno un grosso vantaggio: sono molto
precisi con i calcoli lineari.
Hanno una sola grande sfortuna (ma che fa parte della loro natura, non è un
difetto): non funzionano con funzioni non lineari.
Round, Fix, e simili, sono le funzioni non lineari per eccellenza, visto che
azzerano le differenze spostandosi 'bruscamente' verso l'intero. E siccome
la forza dei virgola mobile è proprio la gestione delle micro-differenze, è
chiaro che l'applicazione di operatori non lineari devasta tutto.
Quello che crea disagio in molti, è il fatto che la conversione da Double a
Long, ad esempio, è essa stessa una operazione non lineare, per cui soggetta
a quelle strane 'anomalie' che periodicamente ritornano nel NG. L'importante
è saperlo, e utilizzare i tipi quando opportuno.
Ciao
Alberto

 chiaro che l'applicazione di operatori non lineari devasta tutto.
Allora, spiego il caso pratico.
Ho una query di aggregazione, in cui due campi sono
1) il risultato della giacenza a magazzino, calcolata da VB di volta in 
volta e registrata come valore dell'operazione, es. 31.5 (non ricordo se 
è il valore esatto)
2) la SUM() sui movimenti di magazzino che in teoria mi porta ad avere 
lo stesso valore del campo calcolato di volta in volta, e anche qui ho 31.5
Ora se io ci metto una WHERE campo1 <> campo2 io vedo un record con 
questi due campi entrambi a 31.5... uno dei due quindi mente.
Faccio la round sul secondo (la SUM) e faccio WHERE campo1 <> 
Round(campo2, 3) il record sparisce, quindi i due valori si equivalgono.
Ora, se la round non è lineare e 31.5 è lineare (essendo il valore puro) 
mi aspetterei che quest'ultima operazione di confronto fallisse.
Ciao

 
Esatto! Oppure usi un tipo diverso, dipende dal tipo di 
applicazione/operazioni da effettuare.


 
se i campi su cui fai le operazioni sono floating point la querry 
corretta e':
WHERE (campo1-campo2)>epsilon
epsilon lo stabilisci a priori con qualche algoritmo (vedi link che ho 
gia' postato). Quel che vedi visualizzato non e' uguale a come e' 
"fisicamente" rappresentato. Tu vedi 31,5 ma in realta' il numero e' 
memorizzato come 10010101001...
Quando lo visualizzi passi attraverso un po' di routine del S.O. che 
quel numero lo "convertono" per fartelo vedere in maniera piu' umana.


 questi due campi entrambi a 31.5... uno dei due quindi mente.
No.
E' il solito discorso di memorizzazione e visualizzazione.
La Where opera sulla memorizzazione, ossia sul valore.
Tu 'vedi' la visualizzazione, che il tuo client (che può essere l'Ide di
Access, il Query Analyzer, un tuo sw, ecc...) arrotonda in fase di
conversione da floating point a decimale (perchè l'uomo chiede una
rappresentazione decimale, ma è 'una' rappresentazione, non 'la'
rappresentazione).
Ciao
Alberto

 endif
correggo:
if abs(x-y)<epsilon  '  :)
termoPilucco

 WHERE (campo1-campo2)>epsilon
Insisto: abs (campo1-campo2)>epsilon   :)
termoPilucco

 
Insisti, insisti ... hai tutta mia approvazione!
:-)


 -1.3 che esce da Round è diverso da -1.3 puro.
Con un po' di ritardo... se ti interessa ancora un po'...
Ci sono molteplici modi di rappresentare un numero in virgola mobile e
proprio per evitare questo viene introdotto il vincolo di
normalizzazione. Quindi nel formato IEEE 754 ogni numero ha una ed *una
sola* rappresentazione binaria, che però sarà quasi sempre
un'approssimazione, in quanto i numeri frazionari rappresentabili
esattamente sono molto pochi.
Per chiarire questi "disguidi" bisognerebbe sapere come funziona la
Round.
