C++: doppio, precisione, macchine virtuali e GCC



optimization compiler-construction (3)

AGGIORNARE: Vedi questo post Come gestire l'eccesso di precisione nei calcoli a virgola mobile? Affronta le problematiche della precisione in virgola mobile estesa. Ho dimenticato la precisione estesa in x86. Ricordo una simulazione che avrebbe dovuto essere deterministica, ma ha dato risultati diversi su CPU Intel rispetto alle CPU PowePC. Le cause erano l'architettura di precisione estesa di Intel.

Questa pagina Web spiega come lanciare le CPU Intel in modalità di arrotondamento a doppia precisione: http://www.network-theory.co.uk/docs/gccintro/gccintro_70.html .

Virtualbox garantisce che le operazioni in virgola mobile siano identiche alle operazioni in virgola mobile dell'hardware? Non sono riuscito a trovare una garanzia del genere con una rapida ricerca su Google. Inoltre, non ho trovato la promessa che gli op di vituralbox FP siano conformi a IEEE 754.

Le macchine virtuali sono emulatori che tentano, e principalmente riescono, di emulare un set di istruzioni o un'architettura particolari. Sono solo emulatori, tuttavia, e soggetti alle loro peculiarità di implementazione o ai problemi di progettazione.

Se non lo hai già fatto, pubblica la domanda forums.virtualbox.org e guarda cosa dice la comunità al riguardo.

https://src-bin.com

Ho il seguente codice:

#include <cstdio>
int main()
{
   if ((1.0 + 0.1) != (1.0 + 0.1))
      printf("not equal\n");
    else
      printf("equal\n");
    return 0;
}

Quando è compilato con O3 usando gcc (4.4,4.5 e 4.6) ed eseguito in modo nativo (ubuntu 10.10), stampa il risultato atteso di "uguale".

Comunque lo stesso codice quando compilato come descritto sopra ed eseguito su una macchina virtuale (ubuntu 10.10, immagine virtualbox), emette "non uguale" - questo è il caso quando i flag O3 e O2 sono impostati ma non O1 e sotto. Quando compilato con clang (O3 e O2) ed eseguito sulla macchina virtuale ottengo il risultato corretto.

Capisco che 1.1 non può essere rappresentato correttamente usando il double e ho letto "Quello che ogni scienziato informatico dovrebbe sapere sull'aritmetica in virgola mobile" quindi per favore non indicarmi lì, questa sembra essere una sorta di ottimizzazione che GCC fa in qualche modo non sembra funzionare in macchine virtuali.

Qualche idea?

Nota: lo standard C ++ dice che la promozione del tipo in queste situazioni dipende dall'implementazione, potrebbe essere che GCC stia usando una rappresentazione interna più precisa che quando il test di disuguaglianza viene applicato è vero - a causa della precisione extra?

UPDATE1: La seguente modifica del codice di cui sopra, ora produce il risultato corretto. Sembra a un certo punto, per qualsiasi ragione, GCC disattiva la parola di controllo a virgola mobile.

#include <cstdio>
void set_dpfpu() { unsigned int mode = 0x27F; asm ("fldcw %0" : : "m" (*&mode)); 
int main()
{
   set_dpfpu();
   if ((1.0 + 0.1) != (1.0 + 0.1))
      printf("not equal\n");
    else
      printf("equal\n");
    return 0;
}

UPDATE2: Per chi chiede informazioni sulla natura di espressione const del codice, l'ho modificato come segue, e ancora non riesce quando compilato con GCC. - ma presumo che l'ottimizzatore possa trasformare il seguente in un'espressione const.

#include <cstdio>
void set_dpfpu() { unsigned int mode = 0x27F; asm ("fldcw %0" : : "m" (*&mode)); 
int main()
{
   //set_dpfpu();  uncomment to make it work.
   double d1 = 1.0;
   double d2 = 1.0;  
   if ((d1 + 0.1) != (d2 + 0.1))
      printf("not equal\n");
    else
      printf("equal\n");
    return 0;
}

UPDATE3 Risoluzione: l' aggiornamento di virtualbox alla versione 4.1.8r75467 ha risolto il problema. Tuttavia il loro rimane un problema, cioè: perché il clang build funziona.


Answer #1

Posso confermare lo stesso comportamento del tuo codice non VM, ma poiché non ho una VM non ho testato la parte VM.

Tuttavia, il compilatore, sia Clang che GCC valuteranno l'espressione costante al momento della compilazione. Vedere l'output dell'assieme di seguito (utilizzando gcc -O0 test.cpp -S ):

    .file   "test.cpp"
    .section        .rodata
.LC0:
    .string "equal"
    .text
    .globl  main
    .type   main, @function
main:
.LFB0:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    movl    $.LC0, %edi
    call    puts
    movl    $0, %eax
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE0:
        .size   main, .-main
        .ident  "GCC: (Ubuntu/Linaro 4.6.1-9ubuntu3) 4.6.1"
        .section        .note.GNU-stack,"",@progbits

Sembra che tu capisca l'assemblaggio, ma è chiaro che c'è solo la stringa "uguale", non c'è "non uguale". Quindi il confronto non è nemmeno fatto in fase di esecuzione, ma stampa "uguale".

Vorrei provare a codificare il calcolo e il confronto usando l'assembly e vedere se hai lo stesso comportamento. Se si ha un comportamento diverso sulla VM, è il modo in cui la VM esegue il calcolo.

AGGIORNAMENTO 1: (in base a "UPDATE 2" nella domanda originale). Di seguito è riportato l' gcc -O0 -S test.cpp output gcc -O0 -S test.cpp (per architettura a 64 bit). In essa puoi vedere il movabsq $4607182418800017408, %rax line due volte. Questo sarà per i due flag di confronto, non ho verificato, ma presumo che il valore $ 4607182418800017408 sia 1.1 in termini in virgola mobile. Sarebbe interessante compilarlo sulla VM, se ottieni lo stesso risultato (due linee simili) allora la VM farà qualcosa di divertente in fase di esecuzione, altrimenti è una combinazione di VM e compilatore.

main:
.LFB1:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp
        .cfi_def_cfa_register 6
        subq    $16, %rsp
        movabsq $4607182418800017408, %rax
        movq    %rax, -16(%rbp)
        movabsq $4607182418800017408, %rax
        movq    %rax, -8(%rbp)
        movsd   -16(%rbp), %xmm1
        movsd   .LC1(%rip), %xmm0
        addsd   %xmm1, %xmm0
        movsd   -8(%rbp), %xmm2
        movsd   .LC1(%rip), %xmm1
            addsd   %xmm2, %xmm1
        ucomisd %xmm1, %xmm0
        jp      .L6
        ucomisd %xmm1, %xmm0
        je      .L7

Answer #2

Vedo che hai aggiunto un'altra domanda:

Nota: lo standard C ++ dice che la promozione del tipo in queste situazioni dipende dall'implementazione, potrebbe essere che GCC stia usando una rappresentazione interna più precisa che quando il test di disuguaglianza viene applicato è vero - a causa della precisione extra?

La risposta a questo è no. 1.1 non è esattamente rappresentabile in un formato binario, non importa quanti bit ha il formato. Puoi avvicinarti, ma non con un numero infinito di zeri dopo il .1 .

O intendevi un formato interno completamente nuovo per decimali? No, mi rifiuto di crederlo. Non sarebbe molto compatibile se lo facesse.





vm-implementation