Domanda

Mi è stato il debug il mio progetto e non riuscivo a trovare un bug. Infine ho individuato esso. Guardare il codice. Pensi che tutto è OK, e il risultato sarà "OK! OK! OK!", Non è vero? Ora compilarlo con VC (ho provato VS2005 e VS2008).

#include <math.h>
#include <stdio.h>


int main () {
    for ( double x = 90100.0; x<90120.0; x+=1 )
    {
        if ( cos(x) == cos(x) )
            printf ("x==%f  OK!\n", x);
        else
            printf ("x==%f  FAIL!\n", x);
    }

    getchar();
    return 0; 
}

Il doppio costante magica è 90112,0. Quando x <90.112,0 tutto è OK, quando x> 90.112,0 - No! È possibile modificare cos al peccato.

Tutte le idee? Non dimenticate che il peccato e cos sono periodiche.

È stato utile?

Soluzione

Potrebbe essere questo: http: //www.parashift .com / C ++ - faq-lite / newbie.html # faq-29.18

  

Lo so che è difficile da accettare, ma aritmetica in virgola mobile semplicemente non funziona come la maggior parte delle persone si aspettano. Peggio ancora, alcune delle differenze dipendono i dettagli di hardware del particolare del computer in virgola mobile e / o le impostazioni di ottimizzazione che si utilizzano sul vostro particolare compilatore. Potrebbe non piace che, ma è il modo in cui è. L'unico modo per "farlo" è quello di mettere da parte le vostre ipotesi su come le cose dovrebbe comportarsi e accettare le cose come realmente fare si comportano ...

     

(con l'accento sulla parola "spesso", il comportamento dipende dall'hardware, compilatore, ecc): calcoli in virgola mobile e confronti sono spesso eseguite da uno speciale hardware che spesso contengono registri speciali, e tali registri hanno spesso più bit di un double. Ciò significa che calcoli intermedi in virgola mobile hanno spesso più bit sizeof(double), e quando un valore in virgola mobile viene scritto in RAM, spesso ottiene troncato, spesso perdendo alcuni pezzi di precisione ...

     

solo ricordare questo: il confronto in virgola mobile sono ingannevoli e sottile e pieno di pericoli. Stai attento. Il modo in virgola mobile effettivamente Works è diverso dal modo in cui la maggior parte dei programmatori tendono a pensare che dovrebbe al lavoro. Se si intende utilizzare in virgola mobile, è necessario imparare come funziona realmente ...

Altri suggerimenti

Come altri hanno notato, la libreria matematica VS sta facendo il suo calcolo sulla FPU x87, e generando risultati 80-bit anche se il tipo è doppio.

In questo modo:

  1. cos () è chiamato, e ritorna con cos (x) sulla parte superiore della pila x87 come un galleggiante 80bit
  2. cos (x) viene estratto dallo stack x87 e memorizzato nella memoria come una doppia; questo fa essere arrotondato a galleggiante 64 bit, che cambia il suo valore
  3. cos () è chiamato, e ritorna con cos (x) sulla parte superiore della pila x87 come un galleggiante 80bit
  4. il valore arrotondato viene caricato nello stack x87 dalla memoria
  5. i valori arrotondati e non arrotondate di cos (x) risultano disuguali.

Molte librerie matematiche e compilatori proteggono da questa da uno facendo il calcolo nel galleggiante 64 bit nel SSE registra quando disponibile, o forzare i valori da memorizzare e arrotondato prima del confronto, o memorizzando e ricaricando il risultato finale il calcolo effettivo di cos (). Il / combinazione libreria di compilatore vi capita di lavorare con non è così indulgente.

I cos (x) == cos (x) Procedura generato nella modalità di rilascio:

00DB101A  call        _CIcos (0DB1870h) 
00DB101F  fld         st(0) 
00DB1021  fucompp 

Il valore viene calcolato una volta, e poi clonato, poi confrontato con se stesso - risultato sarà ok

Lo stesso in modalità debug:

00A51405  sub         esp,8 
00A51408  fld         qword ptr [x] 
00A5140B  fstp        qword ptr [esp] 
00A5140E  call        @ILT+270(_cos) (0A51113h) 
00A51413  fld         qword ptr [x] 
00A51416  fstp        qword ptr [esp] 
00A51419  fstp        qword ptr [ebp-0D8h] 
00A5141F  call        @ILT+270(_cos) (0A51113h) 
00A51424  add         esp,8 
00A51427  fld         qword ptr [ebp-0D8h] 
00A5142D  fucompp          

Ora, accadono cose strane.
1. X viene caricato sul fstack (X, 0)
2. X è memorizzato sulla pila normale (troncamento)
3. coseno viene calcolato, risultati su galleggiante pila
4. X viene caricato di nuovo
5. X è memorizzato sulla pila normale (troncamento, come per ora, siamo "simmetrica")
6. Il risultato del 1 ° cosine che era nello stack viene memorizzato nella memoria, ora, un altro troncamento si verifica per il 1 ° valore
7. coseno viene calcolato, secondo risultato se sul galleggiante stack, ma questo valore è stato troncato sola volta
8. 1 ° valore viene caricato sul fstack, ma questo valore è stato troncato per due volte (una volta prima di calcolare il coseno, una volta dopo)
9. Tali valori vengono confrontati 2 -. Stiamo ottenendo gli errori di arrotondamento

Si dovrebbe mai non confrontare raddoppia per l'uguaglianza nella maggior parte dei casi. Non si può ottenere ciò che vi aspettate.

registri in virgola mobile possono avere una dimensione diversa valori di memoria (nelle macchine attuali Intel, registri FPU sono 80 bit vs 64 bit raddoppia). Se il compilatore genera codice che calcola il primo coseno, quindi memorizza il valore in memoria, calcola il secondo coseno e confronta il valore in memoria da quello nel registro allora i valori potrebbero differire (a causa dell'arrotondamento problemi da 80 a 64 bit) .

I valori in virgola mobile sono un po 'complicato. Google per floating point comparissons.

Il compilatore potrebbe aver generato codice che finisce confrontando un valore doppio 64-bit, con registro virgola mobile interno di 80 bit. Verificare valori di virgola mobile per la parità è incline a questo tipo di errori - si è quasi sempre meglio fare un confronto "fuzzy" come (FAB (val1 - val2) .

Incremento e testare un valore float come variabile di controllo è generalmente un'idea veramente Bad. Creare un int LCV separato solo per il ciclo in poi, se si deve.

In questo caso è più facile:

for ( int i = 90100; i<90120; i+=1 )    {
    if ( cos(i) == cos(i) )
        printf ("i==%d  OK!\n", i);
    else
        printf ("i==%d  FAIL!\n", i);
}

Come intorno problema? Modificare se di blocco:

if ( (float)cos(x) == (float)cos(x) )
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top