2012-11-12 16 views
16

Napotkałem coś nieco mylącego podczas próby radzenia sobie z arytmetycznym problemem zmiennoprzecinkowym.Dlaczego GDB ocenia arytmetykę zmiennoprzecinkową w inny sposób niż C++?

Najpierw kod. Mam destylowana istotę mojego problemu w tym przykładzie:

#include <iostream> 
#include <iomanip> 

using namespace std; 
typedef union {long long ll; double d;} bindouble; 

int main(int argc, char** argv) { 
    bindouble y, z, tau, xinum, xiden; 
    y.d = 1.0d; 
    z.ll = 0x3fc5f8e2f0686eee; // double 0.17165791262311053 
    tau.ll = 0x3fab51c5e0bf9ef7; // double 0.053358253178712838 
    // xinum = double 0.16249854626123722 (0x3fc4ccc09aeb769a) 
    xinum.d = y.d * (z.d - tau.d) - tau.d * (z.d - 1); 
    // xiden = double 0.16249854626123725 (0x3fc4ccc09aeb769b) 
    xiden.d = z.d * (1 - tau.d); 
    cout << hex << xinum.ll << endl << xiden.ll << endl; 
} 

xinum i xiden powinny mieć taką samą wartość (gdy y == 1), ale z powodu zmiennoprzecinkowej błąd zaokrąglenia nie. Ta część dostaję.

Pojawiło się pytanie, kiedy uruchomiłem ten kod (w rzeczywistości, mój prawdziwy program) za pośrednictwem GDB, aby wyśledzić rozbieżność. Jeśli używam GDB odtworzyć oceny dokonane w kodzie, to daje inny wynik dla xiden:

$ gdb mathtest 
GNU gdb (Gentoo 7.5 p1) 7.5 
... 
This GDB was configured as "x86_64-pc-linux-gnu". 
... 
(gdb) break 16 
Breakpoint 1 at 0x4008ef: file mathtest.cpp, line 16. 
(gdb) run 
Starting program: /home/diazona/tmp/mathtest 
... 
Breakpoint 1, main (argc=1, argv=0x7fffffffd5f8) at mathtest.cpp:16 
16   cout << hex << xinum.ll << endl << xiden.ll << endl; 
(gdb) print xiden.d 
$1 = 0.16249854626123725 
(gdb) print z.d * (1 - tau.d) 
$2 = 0.16249854626123722 

Zauważysz, że jeśli pytam GDB obliczyć z.d * (1 - tau.d), to daje 0.16249854626123722 (0x3fc4ccc09aeb769a), natomiast rzeczywisty kod C++, który oblicza to samo w programie, daje 0,16249854626123725 (0x3fc4ccc09aeb769b). Zatem GDB musi używać innego modelu oceny dla arytmetyki zmiennoprzecinkowej. Czy ktoś może rzucić więcej światła na ten temat? Czym różni się ocena GDB od oceny mojego procesora?

Spojrzałem na this related question pytając o GDB oceniając sqrt(3) na 0, ale to nie powinno być to samo, ponieważ nie ma tu żadnych wywołań funkcji.

Odpowiedz

2

To nie jest GDB w porównaniu z procesorem, jest to pamięć w porównaniu z procesorem. Procesor x64 przechowuje więcej bitów dokładności niż pamięć faktycznie utrzymuje (80ish vs 64 bity). Tak długo, jak pozostaje w CPU i rejestruje się, zachowuje 80-tki bitów dokładności, ale kiedy zostanie wysłane do pamięci, określi, kiedy iw związku z tym zostanie zaokrąglone. Jeśli GDB wysyła wszystkie przerywane wyniki obliczeń z CPU (nie mam pojęcia, czy tak jest, czy gdziekolwiek blisko), wykona zaokrąglanie pod co krok, co prowadzi do nieco innych wyników.

5

Możliwe, ponieważ FPU x86 pracuje w rejestrach z dokładnością do 80 bitów, ale zaokrągla do 64 bitów, gdy wartość jest przechowywana w pamięci. GDB będzie zapisywał do pamięci na każdym etapie obliczeń (interpretowanych).

+0

W rzeczywistości wynik gdb jest matematycznie poprawniejszy, więc wydaje się, że gdb używa większej precyzji FPU, podczas gdy g ++ prawdopodobnie używa instrukcji SSE. –

4

System oceny wyrażeń GDB z pewnością nie gwarantuje wykonania takiego samego efektywnego kodu maszynowego dla operacji zmiennoprzecinkowych, jak zoptymalizowany i zmieniony kod maszynowy wygenerowany przez kompilator w celu obliczenia wyniku tego samego wyrażenia symbolicznego. Rzeczywiście, jest zagwarantowane, że nie wykonamy tego samego kodu maszynowego w celu obliczenia wartości danego wyrażenia z.d * (1 - tau.d), ponieważ może to być uznane za podzbiór programu, dla którego wyodrębniona ocena wyrażenia jest wykonywana w czasie wykonywania w niektórych arbitralnych, "symbolicznie poprawnych". droga.

Generowanie kodu zmiennoprzecinkowego i realizacja jego wyjścia przez procesor jest szczególnie podatna na symboliczną niespójność z innymi implementacjami (takimi jak ewaluator wyrażeń wykonawczych) z powodu optymalizacji (zastępowanie, zmiana kolejności, eliminacja podwyrażeń, itp.), Wybór instrukcji, wybór przydziału rejestru i środowiska zmiennoprzecinkowego. Jeśli twój fragment zawiera wiele automatycznych zmiennych w wyrażeniach tymczasowych (tak jak twoje), generowanie kodu ma wyjątkowo dużą swobodę, a nawet zero optymalizacji przechodzi, a przy tej wolności pojawia się szansa - w tym przypadku - na precyzję w najmniej znaczący bit w sposób, który wydaje się niekonsekwentny.

Nie uzyskasz wiele informacji na temat tego, dlaczego ewaluator środowiska wykonawczego GDB wykonał instrukcje, które zrobił bez głębokiego wglądu w kod źródłowy GDB, ustawienia kompilacji i własny kod generowany w czasie kompilacji.

Można uzyskać szczyt w wygenerowanym złożeniu w celu sprawdzenia, jak działają magazyny końcowe pod z, tau i [w przeciwieństwie] xiden. Przepływ danych dla operacji zmiennoprzecinkowych prowadzących do tych sklepów prawdopodobnie nie jest taki, jak się wydaje.

O wiele łatwiej, spróbuj uczynić generację kodu bardziej deterministyczną poprzez wyłączenie optymalizacji wszystkich kompilatorów (np. -O0 na GCC) i przepisanie wyrażeń zmiennoprzecinkowych, aby nie używać tymczasowych/automatycznych zmiennych. Następnie przerwij każdy wiersz w GDB i porównaj.

Żałuję, że nie mogę powiedzieć dokładnie, dlaczego ten najmniej znaczący kawałek mantysy jest odwrócony, ale prawda jest taka, że ​​procesor nawet nie "wie", dlaczego coś niesie, a coś innego nie wynika z , np. kolejność oceny bez pełnej instrukcji i śledzenia danych zarówno Twojego kodu, jak i samego GDB.

Powiązane problemy