Newsgroup: it.comp.lang.c++
Titolo: Sto diventando scemo

Sto sviluppando un'applicazione C++ / directx.
Tralascio i dettagli e vengo subito al punto.
Compilo con visual c++ express edition.
arrivo a questo punto.
origin.x = 149597870e0;
double pippo = origin.x;
for (int i=0;i<5;i++) {
   pippo   = pippo + 1.0;
}
Ora, origin.x e' di tipo double.
lasciamo perdere la bonta' del codice, e' porcheria perche' sto facendo 
dei test.
Il problema:
prima di partire pippo vale 149597870
pippo al giro 0 vale 149597872 (!!)
pippo al giro 1 vale ancora 149597872
pippo al giro 2 vale ancora 149597872
pippo al giro 3 vale ancora 149597872
pippo al giro 4 vale ancora 149597872
Tutto cio' l'ho verificato anche con il debugger.
Cioe' la prima somma incrementa di 2 anziche' di uno la variabile
e gli altri giri invece non la incrementano proprio!
Se anziche' pippo = pippo + 1 metto pippo = pippo + 100, funziona.
Sembrerebbe un problema di precisione, si incasina.. ma caspita e' 
double!
Creato un progetto da zero, solo main, funziona tutto bene.
Che caspita succede?

 Che caspita succede?
Stai sommando un numero troppo grande a uno troppo piccolo: il risultato 
è una perdita di precisione, per cui se sommi 1.0 più volte la macchina 
arrotonda la prima volta per eccesso a +2 e tutte le successive per 
difetto a +0. Non so se mi sono spiegato...
Con 100 l'ordine di grandezza dei due numeri è "sufficientemente vicino" 
e la somma è corretta.
Con i numeri in virgola mobile bisogna stare attenti... :-)

Massimo Soricetti scriveva il 17/01/2008 :

Ok ma come mai, fatto un nuovo progetto, la medesima somma funziona? E 
funziona anche con + 0.0001 ?
Stesso compilatore, stesso codice.
Inoltre ho usato un double non un float. Possibile che un double canni 
una somma 149 milioni piu' uno? Suvvia la fa una calcolatrice a 8 cifre 
degli anni 70 :)
Infine, che soluzioni ho se davvero fosse questo il problema (ma 
continuerei a non spiegarmi perche dall'altra parte funziona, dovrei 
avere un ugual troncamento)? Esiste qualche libreria o artifizio per 
riuscire ad aver maggior precisione? Insomma programmi di rendering, 
frattali, di matematica, di meccanica ecc.. non possono certo 
permettersi un imprecisione del genere, vi dev'essere il modo.

 Che caspita succede?
Se crei un altro progetto funziona tutto, hmmmm....
Qualche opzione strana nel tuo progetto principale? Qualche 
ottimizzazione "troppo ottimizzante" magari?

 vicino" e la somma è corretta.
Non e' cosi'. Supponendo che stia usando una macchina x86 con unita fp 
hardware,
la precisone di un double e' di 53 bit (piu o meno 16 cifre defimali), e 
uno non e'
un numero 'piccolo'.
Il problema e' piu' probabilmente nella visualizzazione del debugger, 
che 'arrotonda'
i numeri all' unita' piu' prossima, per cui se  il numero fosse 
xxxx69.99999 piu'
uno --> xxxx70.9999999 che il debugger potrebbe arrotondare IN 
VISUALIZZAZIONE
a xxxx71.0000 per cui le' effetto sarebbe di vedere un salto di 2 unita'....
Per prova puoi stampare in valore , che so....
        printf("%30.15f - ",pippo);   // Valore prima della somma
          pippo   = pippo +1.0;
        printf("%30.15f\n",pippo);   // Valore dopo la somma
Bye,
brix

Confermo quanto detto ....
mi è capitata la stessa cosa anche a me.
prova a fare la stampa del valore
std::cout << ....
bye.
brix99luftballons ha scritto:



Mah, e rispondo a tutti... sempre piu' perplesso.
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE prevInstance,
				   PSTR cmdLine, int showCmd)
{
	double pluto = 149597870;
	pluto = pluto + 1;
	pluto = pluto + 1;
	pluto = pluto + 1;  // arrivato qui, il risultato e' 14997873, evviva
[....]
}
void Main::updateScene(float dt)
{
		double pluto = 149597870;
	pluto = pluto + 1;  // qui, il risultato e' 14997872 !!!
	pluto = pluto + 1;
	pluto = pluto + 1;  // arrivato qui, il risultato e' 14997872,
ancora.
non so piu' dove sbattere la testa.

Come suggerito sopra, puoi riportare le opzioni del compilatore che
hai usato?
Mi sembra le trovi con
Project properties -> Configuration Props -> C/C++ -> Command Line

   
Ripeto: sei sicuro di quello che visualizzi?????????????
Il fatto di NON vedere decimali e' sospetto: controlla quale precisione
stai utilizzando, ed in ogni caso il numero memorizzato nel double,
a meno della precisione intrinseca, e' correto (salvo FPU unit guasta ;-)
Con questo esempio, funziona correttamente...........
#include <cstdlib>
#include <iostream>
#include <iomanip>
using namespace std;
int main(int argc, char *argv[])
{
    double pluto = 149597870;
    cout << "Test double" <<  std::endl;
    for(int j; j < 9; j++)
    {
        cout << "(" << setprecision(9) << pluto << ")";
        cout <<  setprecision(40) << pluto << "+1\t=";
          pluto   = pluto + 1;
        cout <<  setprecision(40) << pluto;
        cout <<  setprecision(10) << "\t -->" << pluto << "<--" << endl;
    }
    return EXIT_SUCCESS;
}
Test double
(149597870)149597869.9999999999990000+1    
=149597870.9999999999960000     -->149597871<--
(149597871)149597870.9999999999960000+1    
=149597872.0000000000030000     -->149597872<--
(149597872)149597872.0000000000030000+1    
=149597873.0000000000010000     -->149597873<--
(149597873)149597873.0000000000010000+1    
=149597873.9999999999980000     -->149597874<--
(149597874)149597873.9999999999980000+1    
=149597874.9999999999960000     -->149597875<--
(149597875)149597874.9999999999960000+1    
=149597876.0000000000020000     -->149597876<--
(149597876)149597876.0000000000020000+1    =1495978770000               
               -->149597877<--
(149597877)1495978770000+1                             
=149597877.9999999999980000     -->149597878<--
(149597878)149597877.9999999999980000+1    
=149597878.9999999999960000     -->149597879<--
(149597879)149597878.9999999999960000+1    
=149597880.0000000000020000     -->149597880<--
Nota che la prima e ultima colonna con precisione=9 e 10 non mostrano i 
decimali, sotto il 9
mostrano sempre lo stesso valore........
brix

 Creato un progetto da zero, solo main, funziona tutto bene.
Una bella gatta da pelare. Sei sicuro che nel nuovo progetto hai scritto 
il codice incriminato come nel vecchio? In tal caso il problema è nel 
resto del codice. Unica soluzione: copia il tuo progetto e comincia a 
togliere roba dimezzandolo ogni volta. Ad ogni passo ricontrolli il BUG 
e vedi quando scompare.
E.

 Ripeto: sei sicuro di quello che visualizzi?????????????
Il problema e' che non son praticissimo di programmazione sotto 
windows, questa e' una applicazione che non gira sotto shell, ed un 
cout ovviamente non va.
Ma in ogni caso quel che visualizzo, me lo mostra per l'appunto il 
debugger di VC, metto un breakpoint sul double, una watch su pluto, che 
viene visualizzato con 149597870.00000.
Eseguo un passo e pluto diventa rosso, indice che e' stato modificato, 
e vale 149597872.00000 (questo e' quello che mi mostra sia il debugger 
che uno sprintf %.4f poi buttato a video con directx).
Altro passo, pluto torna nero, come se la somma non avesse avuto 
effetto alcuno, e il risultato resta 149597872.00000, terzo passo idem.
Non credo sia un problema di precisione di stampa visto che di fatto si 
tratta di un programma che muove un'oggetto 3d nello spazio. Se lo 
posiziono a quella distanza dall'origine, i tasti per muoverlo, che di 
fatto aumentano/diminuiscono di 1 la coordinata, smettono di 
funzionare, e col debugger mi sono per l'appunto accorto che il 
problema e' che la coordinata non cambia proprio. Ovvero, posso anche 
mettere pluto = pluto + 1 in un ciclo for da 1 a 1000000, ma non si 
sposta. E' proprio la rappresentazione del numero che sembra non 
riuscire a fare correttamente la somma.
Inizialmente era float, e mi son detto "sara' un problema di 
precisione, aspetta che metto double". Ma nessun effetto.
Preso dalla disperazione ho copiaincollato quel pezzo di codice che hai 
visto, all'inizio del programma, su un'altro metodo, breakpoint li' 
e... funziona.
Semplicemente dentro al metodo updateScene, la stessa identica porzione 
di codice non va, altrove si.
Perche'?? Se fosse un problema di opzioni di compilatore dovrebbe non 
funzionare ovunque, idem se si tratta di problemi di precisione troppo 
spinta, ma... all'inizio del codice funziona, dopo no.
Mah!
Non mi arrendo.
-- 
Diario di un videogioco che non vedrà mai la luce
http://pidigi.blogspot.com/

 ogni passo ricontrolli il BUG e vedi quando scompare.
Gia', voglio proprio iniziare a commentare tutto e tenere solo quelle 
linee li', l'informatica voglio ancora sperare sia una scienza esatta 
:)


Sembra che fnegroni abbia detto :

Ora non ho il pc sottomano con vc, ma senz'altro appena riprendo ad 
imprecarci.


fnegroni ha detto questo giovedì :

eccole
/O2 /GL /I "C:\Documents and Settings\Claudio\Documenti\Visual Studio 
2005\Projects\Tau_Ceti_Map\DXUT\Core" /D "_UNICODE" /D "UNICODE" /FD 
/EHsc /MD /Fo"Release\\" /Fd"Release\vc80.pdb" /W3 /nologo /c /Wp64 /Zi 
/TP /errorReport:prom
-- 
http://pidigi.blogspot.com
Diario di un gioco che non vedrà mai la luce

 /TP /errorReport:prom
Hmmm, quel /O2 mi puzza. Hai provato a toglierlo?
Ho capito bene che stai compilando con le ottimizzazioni attivate e 
facendo il debug del codice prodotto???
Per curiosita', puoi riportare la funzione completa?
  bye
	av.

> /nologo /c /Wp64 /Zi  /TP /errorReport:prom
Fantasioso... da uno scambio di IM con PdG, abbiamo scoperto l'arcano:
Era chiaramente un problema di precisione, e il sospetto iniziale era
che fosse impostata bassa sulle impostazioni del progetto, invece no.
Il codice in questione fa uso di directx, e pare che sia quest'ultimo
(quando inizializzato) ad impostare la precisione della FPU a 24 bit
(ecco perche' eseguendo lo stesso codice in due punti diversi il
risultato cambiava). Resettando la control word della FPU prima
dell'operazione il problema si risolve.
Detto questo, credo a questo punto si sia brutalmente OT :)
--*PaN!*
 
-- 
I'm using an evaluation license of nemo since 151 days.
You should really try it!
http://www.malcom-mac.com/nemo

 Per curiosita', puoi riportare la funzione completa?
Come detto il problema e' risolto, per fortuna.
Tutto nasce comunque da qui: quando premevo W o S, la coordinata X non 
si aggiornava, se X era particolarmente grande.
void Main::updateScene(float dt)
{
	mGfxStats->update(dt);
	POINT s;
	GetCursorPos(&s);
	ScreenToClient(mhMainWnd, &s);
	mouseX = s.x;
	mouseY = s.y;
	gDInput->poll();
	// Check input.
	if( gDInput->keyDown(DIK_W) )
	    origin.x   = origin.x + 25.0f * dt;
	if( gDInput->keyDown(DIK_S) )
		origin.x   = origin.x - 25.0f * dt;
	if( gDInput->keyDown(DIK_A) )
		mCameraX   += 25.0f * dt;
	if( gDInput->keyDown(DIK_D) )
		mCameraX   -= 25.0f * dt;
	if( gDInput->keyDown(DIK_T) )
		mCameraY   += 0.250f * dt;
	if( gDInput->keyDown(DIK_G) )
		mCameraY   -= 0.250f * dt;
	if (gDInput->mouseButtonDown(1))
	{
		// Divide by 50 to make mouse less sensitive.
		mCameraRotationY += gDInput->mouseDX() / 400000.0f;
		mCameraRadius    += gDInput->mouseDY() / 25.0f;
	}
	// If we rotate over 360 degrees, just roll back to 0
	if( fabsf(mCameraRotationY) >= 2.0f * D3DX_PI )
		mCameraRotationY = 0.0f;
	// Don't let radius get too small.
	if( mCameraRadius < 5.0f )
		mCameraRadius = 5.0f;
	// The camera position/orientation relative to world space can
	// change every frame based on input, so we need to rebuild the
	// view matrix every frame with the latest changes.
	buildViewMtx();


 dell'operazione il problema si risolve.
In realtà gli converrebbe usare:
IDirect3D9::(..., ..., ..., D3DCREATE_FPU_PRESERVE, ..., ...)
Che lascia la CQ della FPU intatta. perdi un pelo pelo di fps, ma sono 
inezie. Molto peggio perder precisione nel suo caso, e pure nel nostro.
Ah gia, ma tu sei retired ;)
- r/l -

 (quando inizializzato) ad impostare la precisione della FPU a 24 bit
Per curiosità, sai come sono divisi i bit per mantissa ed esponente?
E.


x86 al momento supporta 3 tipi di floating point, tutti standard IEEE
- 32bit (float) 24bit mantissa, 7 esponente
- 64bit (double) 53bit +8esponente
- 80bit (double extended precision) 64bit
Per la descrizione esaustiva del formato, vedi la descizione IEEE o i 
manuali
della Intel, sono descritti molto bene.
Con la control word puoi forzare la FPU a lavorare a float (32bit, 24bit 
precisione)
double(64bit,53precisione) o extended double (80bit, 64bit precisione)
In piu' si puo' controllare anche l' arrotondamento e il troncamento....
Per cui i programmi sporcaccioni che agiscono sulle modalita' hardware
e poi non ripristinano il contesto possono provocare dei risultati a dir
poco inattesi, come questa discussione dimostra ;-)
brix

 Da solo non ci sarei mai arrivato.
Tutto è bene quel che finisce bene, dice il saggio... :-)
Per curiosità: stai mica trafficando su un progetto opensource? 
Vegastrike, Ogre o simili??

Nel suo scritto precedente, Servizio Tecnico ha sostenuto :

si'.
Lavoro da solo pero', e non ho nessuna velleita' (ne speranza) di 
arrivare alla fine, e' puro hobby senza presunzioni o chissacche'.
Ad ogni modo l'idea sarebbe quella di fare un clone di Frontier, e 
tengo un blog dove posto i progressi giorno per giorno
http://pidigi.blogspot.com
 se sei curioso.
(non e' spam, non ci guadagno un centesimo :) )


 IEEE- 32bit (float) 24bit mantissa, 7 esponente
Non esattamente ;)
Sono 24 di mantissa, 8 di esponente e 1 di segno (totale 33 bit!) :)
Questo perche' dei 24 bit di mantissa, quello piu' alto non e'
rappresentato (si suppone sia sempre 1, il che e' vero sempre tranne
nel caso il numero rappresentato sia zero).
Idem per gli altri formati, c'e' sempre un bit di troppo :)
--*PaN!*
 
-- 
I'm using an evaluation license of nemo since 155 days.


  Ah gia, ma tu sei retired ;)
Eh gia' ;)
--*PaN!*
 
-- 
I'm using an evaluation license of nemo since 155 days.


   
Gia'... essendo implicito lo davo per scontato, visto che non occupa 
spazio ;-)
brix