2010-03-28 9 views
17

Problem.C++ Utrata dokładności zmiennoprzecinkowej: 3015/0,00025298219406977296

Kompilator Microsoft Visual C++ 2005, 32-bitowy system Windows XP sp3, AMD 64 x 2 procesor.

Kod:

double a = 3015.0; 
double b = 0.00025298219406977296; 
//*((unsigned __int64*)(&a)) == 0x40a78e0000000000 
//*((unsigned __int64*)(&b)) == 0x3f30945640000000 
double f = a/b;//3015/0.00025298219406977296; 

wynikiem obliczeń (czyli "F") jest 11917835,000000000 (((unsigned __int64) (& F)) == 0x4166bb4160000000), chociaż należy +11.917.834,814763514 (tj ((unsigned __int64) (& f)) == 0x4166bb415a128aef).
tj. część ułamkowa jest tracona.
Niestety, potrzebuję części ułamkowej, aby była poprawna.

Pytania:
1) Dlaczego tak się dzieje?
2) Jak mogę rozwiązać problem?

Dodatkowe informacje:
0) Wynik zostanie podjęta bezpośrednio z okna „watch” (nie został wydrukowany, i nie zapomnij ustawić precyzję drukowania). Dostarczyłem również zrzut heksowy zmiennej zmiennoprzecinkowej, więc jestem absolutnie pewien wyniku obliczeń.
1) demontaż f = a/b jest:

fld   qword ptr [a] 
fdiv  qword ptr [b] 
fstp  qword ptr [f] 

2) F = 3015/0,00025298219406977296; Rentowności poprawny wynik (f == 11917834.814763514, ((unsigned __int64) (& f)) == 0x4166bb415a128aef), ale wygląda na to, w tym przypadku wynik jest po prostu obliczone podczas kompilacji:

fld   qword ptr [[email protected] (828EA0h)] 
fstp  qword ptr [f] 

So , jak mogę naprawić ten problem?

P.S. Znalazłem tymczasowe obejście (potrzebuję tylko częściowego podziału, więc używam po prostu f = fmod (a/b)/b w tej chwili), ale nadal chciałbym wiedzieć, jak rozwiązać ten problem poprawnie - podwójne precyzja ma być 16 cyfr dziesiętnych, więc takie obliczenia nie powinny powodować problemów.

Odpowiedz

15

używasz DirectX w programie nigdzie jako że powoduje aby przełączyć w tryb pojedynczej precyzji, chyba że wyraźnie powiedzieć to nie podczas tworzenia urządzenia i spowoduje dokładnie to

+1

To jest poprawna odpowiedź. Program używa Direct3D i oczywiście obliczenia odbywają się po utworzeniu urządzenia. Zabawne jest to, że wiedziałem, że D3D dostosowuje precyzję FPU, ale zupełnie o tym zapomniałem, ponieważ nie widziałem tego błędu w ciągu ostatnich kilku lat. Problem rozwiązany. – SigTerm

+1

Jakiej flagi należy użyć podczas tworzenia urządzenia? Czy ten sam problem występuje z Direct2D? – dalle

1

Zgaduję, że drukujesz numer bez precyzowania. Spróbuj tego:

#include <iostream> 
#include <iomanip> 

int main() { 
    double a = 3015.0; 
    double b = 0.00025298219406977296; 
    double f = a/b; 

    std::cout << std::fixed << std::setprecision(15) << f << std::endl; 
    return 0; 
} 

produkuje to:

11917834.814763514000000

który wygląda poprawna do mnie. Używam VC++ 2008 zamiast 2005, ale domyślam się, różnica jest w twoim kodzie, nie kompilatorze.

+0

jednostka zmiennoprzecinkowa Nie, nie drukuję numeru, wynik jest pobierany bezpośrednio z okna "oglądania". – SigTerm

+0

Czy próbowałeś go wydrukować? Może błąd jest w oknie oglądania !! –

+0

@Martin Okno podglądu pokazuje pełną precyzję. –

4

Co ciekawe, jeśli zadeklarujesz zarówno a, jak i b jako pływaki, otrzymasz dokładnie 11917835.000000000. Przypuszczam więc, że jest gdzieś konwersja do pojedynczej precyzji, zarówno w sposobie interpretacji stałych, jak i później w obliczeniach.

Każdy przypadek jest nieco zaskakujący, biorąc pod uwagę, jak prosty jest twój kod. Nie używasz żadnych egzotycznych dyrektyw kompilujących, wymuszających pojedynczą precyzję dla wszystkich liczb zmiennoprzecinkowych?

Edycja: Czy rzeczywiście potwierdziłeś, że skompilowany program generuje niepoprawny wynik? W przeciwnym razie najbardziej prawdopodobnym kandydatem do (błędnej) konwersji pojedynczej precyzji byłby debugger.

+0

Nie ma rzucania do pojedynczej precyzji, jak wyraźnie pokazuje demontaż. –

+0

W każdym razie nie w tych trzech linijkach. –

2

Jeśli potrzebujesz dokładnej matematyki, nie używaj zmiennoprzecinkowej.

Zrób sobie przysługę i zdobądź bibliotekę BigNum z racjonalną obsługą numerów.

+3

Nie potrzebuje 11917834.814763514100059144562708, potrzebuje tylko 11917834.814763514. Wydawanie rzędów wielkości w wydajności i pamięci, aby uzyskać precyzję wbudowaną w maszynę, wydaje się nieco irracjonalne (przebaczenie). – Gabe

+0

Oczywiście, nie jesteśmy uprawnieni do oczekiwania dokładności, ale wciąż mamy prawo prosić o taki poziom poprawności, jaki obiecuje nam specyfikacja zmiennoprzecinkowa! – AakashM

+0

Bez urazy, ale myślę, że używanie bignum tylko do jednego obliczenia jest trochę za dużo, przynajmniej w tym przypadku. – SigTerm

0

Czy jesteś pewien, że sprawdzasz wartość f zaraz po instrukcji fstp? Jeśli masz włączone optymalizacje, być może okno zegarka może pokazywać wartość wziętą w jakimś późniejszym punkcie (wydaje się to trochę prawdopodobne, gdy mówisz, że patrzysz na frakcjonalną część f później - czy jakaś instrukcja kończy się maskowaniem jej jakoś?)

Powiązane problemy