2015-07-14 10 views
18

sto compilare ed eseguire il seguente programma in 32 e 64 bit piattaforme:doppio moltiplicazione diverso tra fase di compilazione e di esecuzione a 32 bit piattaforma

int main() 
{ 
    double y = 8.34214e08; 
    double z = 1.25823e45; 

    return y * z == 8.34214e08 * 1.25823e45; 
} 

Mentre in 64bit il risultato è l'atteso (i valori sono lo stesso e il codice di uscita è diverso da zero) in 32 bit sembra che ci sia una piccola differenza tra il valore calcolato al momento della compilazione, il lato destro del confronto e il lato sinistro calcolato in fase di esecuzione.

Si tratta di un bug nel compilatore o di una spiegazione logica?

MODIFICA: questo è diverso da Why comparing double and float leads to unexpected result? perché qui tutti i valori sono doppi.

+1

@waj: Non ho inoltrato la domanda. È una bella domanda. –

+0

@SteveSummit ok, scusami per questo, ho ottenuto il downvote allo stesso tempo in cui hai risposto e StackOverflow non mostra chi ha votato. Ad ogni modo, posso conviverci, ma ho davvero bisogno di sapere le ragioni, ecco perché sto inviando una domanda qui in primo luogo. – waj

+0

@Giorgi: in quella domanda c'è una differenza perché la variabile è dichiarata come "float" ei valori letterali sono "double". Qui tutti i valori sono doppi. – waj

risposta

19

IEEE-754 consente di eseguire calcoli intermedi con maggiore precisione (enfasi sul mio).

(IEEE-754: 2008). "Uno standard linguaggio deve anche definire, e richiedono implementazioni di fornire, attributi che permettere e impedire che le ottimizzazioni di valore che cambia, separatamente o insieme, per un blocco Queste ottimizzazioni potrebbero includere, ma non sono limitati a: [...] Uso di risultati intermedi più ampi nella valutazione dell'espressione. "

Nel vostro caso ad esempio su un IA-32, i valori doppi potrebbero essere memorizzati nei registri FPU x87 con maggiore precisione (80-bit invece di 64). Quindi in realtà stai confrontando una moltiplicazione fatta in doppia precisione con una moltiplicazione fatta con precisione doppia estesa.

Per esempio, su x64 in cui il risultato è 1 (FPU x87 non viene utilizzato come SSE è usato al posto), aggiungendo gcc opzione -mfpmath=387 di utilizzare la x87 rende il cambiamento risultato 0 sulla mia macchina.

E se vi chiedete se ciò è consentito anche da C, è:

(C99, 6.3.1.p8) "può essere rappresentato I valori di operandi galleggianti e dei risultati delle espressioni galleggianti in maggiore precisione e portata rispetto a quella richiesta dal tipo; "

+1

Buona spiegazione sui risultati intermedi più ampi. – chux

11

In generale, mai do equality checks with floating point numbers. È necessario verificare se il risultato desiderato differisce dal risultato ottenuto meno di una precisione prestabilita.

Ciò che sta accadendo qui è con ogni probabilità a causa della moltiplicazione eseguita su due diverse "piattaforme": una volta dal codice e una dal compilatore, che può avere una precisione diversa. Questo succede con mostcompilers.

tuo programma sarebbe probabilmente lavoro se si è compilato con le stesse opzioni che sono stati utilizzati per compilare il compilatore (supponendo che il compilatore è stato compilato da solo). Ma questo non significa che otterresti il ​​risultato corretto; si otterrebbe lo stesso errore di precisione che il compilatore sta ottenendo.

(Inoltre, sto supponendo che il compilatore esegue una moltiplicazione dritto e il codice di analisi riconoscere carri non entra nell'equazione. Questo potrebbe anche essere un pio desiderio da parte mia).

Testing

Using built-in specs. 
COLLECT_GCC=gcc 
COLLECT_LTO_WRAPPER=/usr/lib64/gcc/x86_64-suse-linux/4.8/lto-wrapper 
Target: x86_64-suse-linux 
Configured with: ../configure --prefix=/usr --infodir=/usr/share/info --mandir=/usr/share/man --libdir=/usr/lib64 --libexecdir=/usr/lib64 --enable-languages=c,c++,objc,fortran,obj-c++,java,ada --enable-checking=release --with-gxx-include-dir=/usr/include/c++/4.8 --enable-ssp --disable-libssp --disable-plugin --with-bugurl=http://bugs.opensuse.org/ --with-pkgversion='SUSE Linux' --disable-libgcj --disable-libmudflap --with-slibdir=/lib64 --with-system-zlib --enable-__cxa_atexit --enable-libstdcxx-allocator=new --disable-libstdcxx-pch --enable-version-specific-runtime-libs --enable-linker-build-id --enable-linux-futex --program-suffix=-4.8 --without-system-libunwind --with-arch-32=i586 --with-tune=generic --build=x86_64-suse-linux --host=x86_64-suse-linux 
Thread model: posix 
gcc version 4.8.3 20141208 [gcc-4_8-branch revision 218481] (SUSE Linux) 



#include <stdio.h> 

int main() 
{ 
    double y = 8.34214e08; 
    double z = 1.25823e45; 

return printf("%s\n", y * z == 8.34214e08 * 1.25823e45 ? "Equal" : "NOT equal!"); 
} 

Forzare -O0 per evitare al compilatore di ottimizzare l'intero codice (grazie @markgz!), Otteniamo

$ gcc -m32 -O0 -o galleggiante float.c & & ./float NON uguale! $ gcc -m32 -frounding-math -O0 -o galleggiare float.c & & ./float Pari

Per la cronaca, dal momento che hai lì davanti a me :-),

-frounding -math

Disabilitare le trasformazioni e le ottimizzazioni che presuppongono il comportamento di arrotondamento a virgola mobile predefinito. Questo è round-to-zero per tutte le conversioni in interi da in virgola mobile e da arrotondato al più vicino per tutte le troncature aritmetiche di . Questa opzione deve essere specificata per i programmi che modificano la modalità di arrotondamento FP in modo dinamico o che potrebbe essere eseguita con una modalità di arrotondamento non predefinita.Questa opzione disabilita il piegamento costante delle espressioni in virgola mobile al momento della compilazione (che può essere influenzato dalla modalità di arrotondamento) e le trasformazioni aritmetiche che non sono sicure in presenza di modalità di arrotondamento dipendenti dal segno.

L'impostazione predefinita è -fno-rounding-math.

+0

Forse dovrei chiarire che sto eseguendo la compilazione e l'esecuzione sulla stessa macchina ogni volta. Sono assolutamente d'accordo sul fatto che, in generale, l'uguaglianza non debba essere usata per i punti fluttuanti. – waj

+0

Stavo per rispondere a qualcosa del genere, ma quello che mi imbarazza davvero è la differenza tra il calcolo in fase di compilazione e il calcolo del tempo di esecuzione. Avrei pensato che GCC eseguisse il calcolo in fase di compilazione usando la stessa precisione/impostazioni del calcolo del tempo di esecuzione o altro, ma in qualche modo il risultato è diverso :) Sono perplesso come @waj –

+0

Ricordo di aver ottenuto risultati strani con ' GCC 'alcuni anni fa, a causa di alcuni "ottimizzazione" matematica che avevo trascurato. Esistono diversi standard "in virgola mobile" e gcc, per esempio, supporta * tutti * di essi. Naturalmente gcc ha funzionato con uno solo: quello con cui è stato compilato. E potrei essere stato un po 'troppo libero quando ho creato la toolchain. Il mio professore era * davvero * non divertito :-) – LSerni

5

I calcoli in virgola mobile eseguiti in fase di compilazione spesso si verificano con una precisione maggiore rispetto agli usi double in fase di esecuzione. Inoltre C può eseguire calcoli intermedi di esecuzione double con la maggiore precisione long double. O spiega la tua disuguaglianza. Vedi FLT_EVAL_METHOD per i dettagli.

volatile double y = 8.34214e08; 
    volatile double z = 1.25823e45; 
    volatile double yz = 8.34214e08 * 1.25823e45; 
    printf("%.20e\n", y); 
    printf("%.20e\n", z); 
    printf("%.20e\n", yz); 
    printf("%.20Le\n", (long double) y*z); 
    printf("%.20Le\n", (long double) 8.34214e08 * 1.25823e45); 

8.34214000000000000000e+08 
1.25822999999999992531e+45 
// 3 different products! 
1.04963308121999993395e+54 
1.04963308121999993769e+54 
1.04963308122000000000e+54 

I risultati potrebbero essere leggermente diversi.

Problemi correlati