2009-09-10 1 views
7

Sto vedendo qualcosa di strano con l'archiviazione dei doppi in un dizionario, e sono confuso sul perché.In che modo un C# valuta il punto mobile al passaggio del mouse sopra e finestra intermedia rispetto a compilato?

Ecco il codice:

  Dictionary<string, double> a = new Dictionary<string, double>(); 
      a.Add("a", 1e-3); 

      if (1.0 < a["a"] * 1e3) 
       Console.WriteLine("Wrong"); 

      if (1.0 < 1e-3 * 1e3) 
       Console.WriteLine("Wrong"); 

La seconda istruzione if funziona come previsto; 1.0 non è inferiore a 1.0. Ora, la prima istruzione if viene valutata come vera. La cosa molto strana è che quando aleggi il puntatore del mouse sopra, l'intellisense mi dice che è falso, eppure il codice si sposta felicemente su Console.WriteLine.

Questo è per C# 3.5 in Visual Studio 2008.

È questo un punto problema precisione floating? Allora perché funziona la seconda istruzione if? Sento che mi manca qualcosa di molto fondamentale qui.

Qualsiasi intuizione è apprezzata.

Edit2 (Ri-proposizione domanda un po '):

posso accettare il problema di precisione matematica, ma la mia domanda ora è: perché il passaggio del mouse sopra valutare correttamente? Questo vale anche per la finestra intermedia. Incollo il codice dalla prima istruzione if nella finestra intermedia e viene valutato false.

Aggiornamento

Prima di tutto, grazie mille per tutte le grandi risposte.

Ho anche problemi a ricreare questo in un altro progetto sulla stessa macchina. Guardando le impostazioni del progetto, non vedo differenze. Guardando l'IL tra i progetti, non vedo differenze. Guardando allo smontaggio, non vedo differenze apparenti (oltre agli indirizzi di memoria). Eppure, quando metto a punto il progetto originale, vedo: screenshot of problem http://i30.tinypic.com/ega874.png

finestra immediata mi dice che il caso è falso, ma il codice cade nella condizionale.

In ogni caso, la risposta migliore, penso, è quella di prepararsi all'aritmetica in virgola mobile in queste situazioni. Il motivo per cui non ho potuto lasciarlo andare ha più a che fare con i calcoli del debugger che differiscono dal runtime. Quindi grazie infinite a Brian Gideon e stephentyrone per alcuni commenti molto perspicaci.

+0

"1e3" è una costante mobile o una doppia costante? –

+0

Se riesci ad accettare il problema della precisione matematica, perché stai ancora chiedendo? Il problema è che 1e-3 non può essere rappresentato con precisione, e quindi 1e3 * 1e-3 non è 1.0, è qualcosa di simile, ma non del tutto, il che significa che fallisce. –

+0

"1e3" viene compilato come un doppio perché vedo l'istruzione ldc.r8 nel mio output IL. –

risposta

13

È un problema di precisione mobile.

Seconda istruzione perché il compilatore conta l'espressione 1e-3 * 1e3 prima di emettere l'exe.

cercarlo nella ILDASM/Reflector, emetterà qualcosa come

if (1.0 < 1.0) 
       Console.WriteLine("Wrong"); 
+0

Strano.La seconda affermazione viene completamente ottimizzata dal compilatore per me. Non sono ancora convinto che si tratti comunque di un problema di precisione in virgola mobile. Vedi la mia risposta. –

+0

+1. D'accordo non riesco a riprodurre il problema no "Wrong" sono scritti sulla console. – AnthonyWJones

+0

VS indica anche che il contenuto del secondo se non è raggiungibile. – AnthonyWJones

2

Umm ... strano. Non sono in grado di riprodurre il tuo problema. Sto usando C# 3.5 e Visual Studio 2008 pure. Ho digitato nell'esempio esattamente come è stato pubblicato e non vedo l'esecuzione dell'istruzione Console.WriteLine.

Inoltre, la seconda istruzione if viene ottimizzata dal compilatore.Quando esamino entrambi i build di debug e release in ILDASM/Reflector, non ne vedo alcuna prova. Da allora perché ricevo un avviso del compilatore che dice che è stato rilevato un codice irraggiungibile.

Infine, non vedo come questo potrebbe essere un problema di precisione a virgola mobile comunque. Perché il compilatore C# dovrebbe valutare staticamente due doppi in modo diverso rispetto al CLR in fase di runtime? Se fosse davvero così, allora si potrebbe argomentare che il compilatore C# ha un bug.

Edit: Dopo aver dato questo un po 'più di pensiero sono ancora più convinto che questa sia non un problema di punto di precisione floating. Devi essere incappato in un errore nel compilatore o nel debugger o il codice che hai postato non è esattamente rappresentativo del tuo codice reale in esecuzione. Sono molto scettico su un bug nel compilatore, ma un bug nel debugger sembra più probabile. Prova a ricostruire il progetto ed eseguirlo di nuovo. Forse le informazioni di debug compilate insieme a exe sono fuori sincrono o qualcosa del genere.

+0

Grazie mille Brian per l'input; il mio collega in fondo al corridoio vede la stessa cosa sulla sua macchina. Windows XP su Dell Optplex 320, Pentium D 3.4 GHz. –

+0

Quale versione di VS hai? La finestra di dialogo about dice 9.0.30729.1 per me. Sto anche utilizzando .NET Framework 3.5 con SP 1. Inoltre, verifica che il framework 3.5 sia destinato alla creazione dell'applicazione. Sembra che stia accadendo qualcosa di insolito. –

4

Il problema qui è piuttosto sottile. Il compilatore C# non emette (sempre) codice che esegue il calcolo in doppio, anche quando è il tipo che hai specificato. In particolare, emette il codice che esegue il calcolo in precisione "estesa" utilizzando le istruzioni x87, senza arrotondare i risultati intermedi per raddoppiare.

seconda che 1e-3 viene valutata come un doppio o long double, e se la moltiplicazione è calcolata in doppia lungo doppio, è possibile ottenere una qualsiasi delle seguenti tre risultati:

  • (long double) 1e3 1e3 * calcolato in long double è 1,0 - epsilon
  • (doppia) 1e3 1e3 * calcolato in doppia è esattamente 1,0
  • (doppia) 1e3 1e3 * calcolato in long double è 1.0 + epsilon

Chiaramente, il primo confronto, quello che non riesce a soddisfare le vostre aspettative, viene valutato nel modo descritto nel terzo scenario che ho elencato. 1e-3 viene arrotondato per raddoppiare o perché lo si sta memorizzando e caricandolo di nuovo, il che costringe l'arrotondamento o perché C# riconosce 1e-3 come un letterale a doppia precisione e lo tratta in questo modo. La moltiplicazione viene valutata in doppio lungo perché C# ha un modello numerico cerebrale morto che è il modo in cui il compilatore sta generando il codice.

La moltiplicazione nel secondo confronto è in corso di valutazione utilizzando uno degli altri due metodi, (È possibile capire quale tentativo provando "1> 1e-3 * 1e3"), oppure il compilatore sta arrotondando il risultato del moltiplicazione prima di confrontarla con 1.0 quando valuta l'espressione in fase di compilazione.

È probabile che tu possa dire al compilatore di non usare la precisione estesa senza che tu lo dica tramite alcune impostazioni di costruzione; abilitare anche i codegen su SSE2.

+0

+1 per una spiegazione completa del problema di precisione –

+0

Prospettiva interessante. Non vedo alcun parametro di compilazione relativo a questo problema né ho scoperto alcuno che potessi cambiare che mi permettesse di riprodurre il problema. Se questo è davvero il problema, sembrerebbe che giacciono nel compilatore JIT in quanto è ciò che sta emettendo le istruzioni della macchina. Ma, se questo è il caso, allora sicuramente è un bug. Dopotutto, avere un programma la cui esecuzione è non deterministica dal punto di vista del design è terribilmente inquietante. –

+0

È (purtroppo) una delle modalità di valutazione consentite per virgola mobile per lo standard C99. Non so se la specifica C# attacca la semantica con più attenzione, ma sarei sorpreso. Su una macchina x86 che non implementa le istruzioni SSE2, è l'unico modo performante per eseguire calcoli a doppia precisione (l'alternativa è memorizzare i risultati dopo ogni fase del calcolo, che distrugge completamente le prestazioni). Non so se l'OP ha una tale macchina, o se C# supporta tali macchine, ma è certamente una possibilità –