Dlaczego rozdzielczość liczb zmiennoprzecinkowych zmniejsza się dalej od początku?

19

Moja scena OpenGL ma obiekty, które są absurdalnie oddalone od źródła. Kiedy oglądam te obiekty i obracam / obracam / powiększamy kamerę wokół nich, „drgają”. Oznacza to, że wierzchołki składające się z obiektów wydają się owijać wokół wyobrażonej trójwymiarowej siatki punktów. Przeczytałem, że jest to powszechny problem z powodu ilości informacji, które można przechowywać za pomocą precyzji zmiennoprzecinkowej (której używa OpenGL i prawie wszystko inne). Nie rozumiem jednak, dlaczego tak się dzieje.

Szukając rozwiązania, natknąłem się na bardzo prostą poprawkę „swobodnego pochodzenia” i wydaje się, że działa. Po prostu przekształcam wszystko, aby moje obiekty znajdowały się w tych samych względnych pozycjach, ale wszystko, na co patrzy mój aparat, jest blisko początku. Znalazłem wyjaśnienie tutaj: http://floatingorigin.com/ , ale nie mogłem go zastosować.

Więc ... Czy ktoś mógłby wyjaśnić, dlaczego umieszczenie mojej sceny bardzo daleko (powiedzmy 10 milionów jednostek) od źródła powoduje nieprawidłowe zachowanie, które zaobserwowałem? A także dlaczego zbliżenie go do źródła rozwiązuje problem?

Pris
źródło
4
Ponieważ gdyby tego nie zrobili, byliby liczbami o ustalonych punktach. Pytanie tautologiczne.
MSalters
1
To prawda, ale tylko wtedy, gdy zrozumiesz, co tak naprawdę oznacza „zmiennoprzecinkowa”.
Kylotan

Odpowiedzi:

26

To WSZYSTKO ze względu na sposób, w jaki zmiennoprzecinkowe są reprezentowane w komputerach.

Liczby całkowite są przechowywane bardzo prosto; każda jednostka jest dokładnie „jedna” oprócz „poprzedniej”, tak jak można się spodziewać po liczbach policzalnych.

W przypadku liczb zmiennoprzecinkowych tak nie jest. Zamiast tego kilka bitów wskazuje na EKSPONENT, a pozostałe wskazują tak zwaną mantysę lub część ułamkową, która następnie MULTIPLIED przez część wykładniczą (domyślnie 2 ^ exp), aby dać końcowy wynik.

Spójrz tutaj, aby uzyskać wizualne wyjaśnienie bitów.

Właśnie dlatego, że wykładnik ten jest faktyczną częścią bitów, precyzja zaczyna zanikać, gdy liczby rosną.

Aby zobaczyć to w akcji, wykonajmy fałszywą reprezentację zmiennoprzecinkową bez wchodzenia w drobiazgowość: weź mały wykładnik jak 2 i zrób kilka ułamkowych części, aby przetestować:

2 * 2 ^ 2 = 8

3 * 2 ^ 2 = 12

4 * 2 ^ 2 = 16

...itp.

Liczby te nie rosną bardzo daleko od samego wykładnika 2. Ale teraz spróbujmy wykładnika 38:

2 * 2 ^ 38 = 549755813888

3 * 2 ^ 38 = 824633720832

4 * 2 ^ 38 = 1099511627776

Zaraz, ogromna różnica teraz!

Przykład, chociaż nie chodzi konkretnie o BARDZO NASTĘPNY LICZNIK (byłaby to następna część ułamkowa w zależności od liczby bitów), ma na celu wykazanie utraty precyzji, gdy liczby rosną. Jednostka „następnej do policzenia” w liczbach zmiennoprzecinkowych jest bardzo mała z małymi wykładnikami i BARDZO duża z większymi wykładnikami, natomiast w liczbach całkowitych ZAWSZE 1.

Powodem, dla którego działa metoda zmiennoprzecinkowa, jest skalowanie wszystkich tych liczb zmiennoprzecinkowych potencjalnie dużych wykładników W DÓŁ DO małych wykładników, dzięki czemu „następne policzenia” (precyzja) mogą być bardzo małe i szczęśliwe.


źródło
Podane przykłady były naprawdę ilustracyjne, dzięki :)
Pris
3
Na dobrej drodze, ale szkoda, że ​​nie użyłeś przykładów, które są bliżej tego, jak naprawdę działa zmiennoprzecinkowy. Nie podnosi mantysy do potęgi; to wykładnik mantysy * 2 ^.
Nathan Reed
3
Masz rację, wiedziałem o tym; Nie wiem o czym myślałem. Edytowałem moją odpowiedź.
1
@ScottW Nice edit! +1
Nathan Reed
17

Ponieważ liczby zmiennoprzecinkowe są reprezentowane jako ułamek + wykładnik + znak, a ty masz tylko określoną liczbę bitów dla części ułamkowej.

http://en.wikipedia.org/wiki/Single_precision

Gdy otrzymujesz coraz większe liczby, po prostu nie masz bitów reprezentujących mniejsze porcje.

Tetrad
źródło
8

Należy wychować klasykę w tej dziedzinie: co każdy informatyk powinien wiedzieć o liczbach zmiennoprzecinkowych .

Ale sedno tego ma związek z tym, że pojedyncze (podwójne) liczby zmiennoprzecinkowe są tylko 32-bitową (64-bitową) liczbą binarną, z 1 bitem reprezentującym znak, 8-bitowym (11-bitowym) wykładnikiem podstawy 2 , i znaczenie 23-bitowe (52-bitowe) (w nawiasach są wartości dla podwójnych).

Oznacza to, że najmniejsza liczba dodatnia, którą można przedstawić w pojedynczej precyzji, to 0,0000000000000000000001 x 2 -127 = 2 -22 x 2 -127 = 2 -149 ~ 1,40 x 10 -45 .

Następna liczba dodatnia jest podwójna: 0,0000000000000000000010 x 2 -127 = 2 -148 ~ 2,80 x 10 -45 , a następnie następna liczba jest sumą dwóch poprzednich 0,0000000000000000000011 x 2 -127 = 3 x 2-149 ~ 4,2 - 45 .

W dalszym ciągu wzrasta w tym samym różnicę stałej do: 0,1111111111111111111111 x 2 -127 = 2 -126 - 2 149 ~ 1,17549435 x 10 -38 - 0,00000014 X 10 -38 = 1.17549421 x 10 -38

Teraz masz już normalne numery (gdzie pierwsza cyfra w mantysy wynosi 1) w szczególności: 1,0000000000000000000000 x 2 -126 = 2 -126 = 1,17549435 x 10 -38 a następny numer jest następnie 1,0000000000000000000001 x 2 -126 = 2 -126 (1 + 2 -22 ) = 1.17549435 x 1.00000023.

dr jimbob
źródło
2

Powodem, dla którego liczby zmiennoprzecinkowe stają się mniej dokładne od początku, jest to, że liczba zmiennoprzecinkowa powinna być w stanie reprezentować duże liczby. Sposób, w jaki to się dzieje, nadaje mu termin „zmiennoprzecinkowy”. Dzieli możliwe wartości, które może przyjąć (które są określane na podstawie długości bitów), tak że dla każdego wykładnika występuje mniej więcej taka sama liczba: dla liczby 32-bitowej liczba zmiennoprzecinkowa 23 bity określają mantysę lub znaczenie. Będzie więc mógł przyjąć wartość 2 ^ 23 różnych wartości w każdym zakresie wykładniczym. Jednym z tych przedziałów wykładniczych jest 1-2 [2 ^ 0 do 2 ^ 1], więc podzielenie zakresu od 1 do 2 na 2 ^ 23 różnych wartości pozwala na dużą precyzję.

Ale podzielenie zakresu [2 ^ 10 na 2 ^ 11] na 2 ^ 23 różnych wartości oznacza, że ​​odstęp między każdą wartością jest znacznie większy. Gdyby tak nie było, 23 bity nie wystarczyłyby. Cała sprawa jest kompromisem: potrzebujesz nieskończonej liczby bitów do reprezentowania dowolnej liczby rzeczywistej. Jeśli aplikacja działa w sposób, który pozwala uzyskać niższą precyzję w przypadku większych wartości, a czerpanie korzyści z możliwości rzeczywistego przedstawiania dużych wartości jest możliwe , użyj reprezentacji zmiennoprzecinkowej.

Steven Lu
źródło
po prostu zanotowałem tutaj po dalszej analizie 7 lat później ... moje liczby w moich przykładach nie są szczególnie dobrze wybrane. Ale ogólne punkty są ważne.
Steven Lu
1

Trudno jest podać konkretne przykłady działania precyzji zmiennoprzecinkowej. Aby uzupełnić pozostałe odpowiedzi, oto jedna. Załóżmy, że mamy dziesiętną liczbę zmiennoprzecinkową z trzema cyframi mantysy i jedną cyfrą wykładnika:

mantysa × 10 wykładnik potęgi

Gdy wykładnik wynosi 0, każda liczba całkowita z zakresu 0–999 może być dokładnie reprezentowana. Gdy wynosi 1, zasadniczo mnożymy każdy element tego zakresu przez 10, więc otrzymujesz zakres 0–9990; ale teraz tylko wielokrotności 10 mogą być dokładnie przedstawione, ponieważ nadal masz tylko trzy cyfry dokładności. Gdy wykładnik wynosi maksymalnie 9, różnica między każdą parą reprezentatywnych liczb całkowitych wynosi miliard . Dosłownie wymieniasz precyzję dla zasięgu.

Działa to samo z binarnymi liczbami zmiennoprzecinkowymi: ilekroć wykładnik wzrośnie o jeden, zakres podwaja się , ale liczba reprezentowalnych wartości w tym zakresie jest zmniejszona o połowę . Dotyczy to również liczb ułamkowych, które są oczywiście źródłem twojego problemu.

Jon Purdy
źródło
0

Zasadniczo rozdzielczość pogarsza się, ponieważ rozdzielczość mnoży się przez wartość wykładnika (2 ** część wykładnika).

w uznaniu dla komentarza Josha: powyższe miało po prostu ułożyć odpowiedź w zwięzłym stwierdzeniu. Oczywiście, jak starałem się wskazać na http://floatingorigin.com/ , to dopiero zaczyna się ogólne rozwiązanie, a Twój program może mieć fluktuacje z wielu miejsc: w potoku precyzji lub innych częściach kodu .

Chris Thorne
źródło
Nie dodaje to niczego, czego nie ma jeszcze w innych odpowiedziach.
Prawda: zdałem sobie sprawę, że mogę opisać odpowiedź w jednym wierszu i pomyślałem, że ktoś może uznać zwięzłą odpowiedź za przydatną.
Chris Thorne
-1

Bufor głębokości OpenGL nie jest liniowy . Im dalej, tym gorsza rozdzielczość. Polecam przeczytać ten . Coś stamtąd wzięte (12.070):

Podsumowując, podział perspektywy z natury powoduje większą precyzję Z w pobliżu przedniej części widoku niż w pobliżu tylnej.

I jeszcze jeden (12.040):

Możliwe, że skonfigurowałeś płaszczyzny odcinania zNear i zFar w sposób, który poważnie ogranicza dokładność bufora głębokości. Zasadniczo jest to spowodowane wartością płaszczyzny odcinania zNear, która jest zbyt bliska 0,0. Ponieważ płaszczyzna odcinania zNear jest coraz bliżej wartości 0,0, efektywna precyzja bufora głębokości dramatycznie spada. Przesunięcie płaszczyzny odcinania zFar dalej od oka zawsze ma negatywny wpływ na precyzję bufora głębokości, ale nie jest tak dramatyczne, jak przesunięcie płaszczyzny odcinania zNear.

Więc powinieneś przesunąć swoją najbliższą płaszczyznę tnącą jak najdalej, a najdalszą płaszczyznę jak najbliżej.

zacharmarz
źródło
-1: pytanie dotyczy precyzji zmiennoprzecinkowej, a nie problemów precyzji z nieliniową reprezentacją bufora głębokości.
Nathan Reed
Możliwe, że to, co widzę, wynika z problemów z buforowaniem głębokości. Korzystam z biblioteki lib na OpenGL, aby wyświetlić moją scenę i zakładam, że konfiguruje kamerę, widok oraz bliskie i dalekie płaszczyzny obcinania, aby uwzględnić rozmiar i położenie geometrii (od przeglądarki narzędzie wydaje się automatycznie ustawiać optymalny widok dla zawartości sceny). Ale chyba tak nie jest - spróbuję bawić się płaszczyznami tnącymi, pozostawiając nienaruszoną pierwotną pozycję i zobaczę, co się stanie.
Pris,
2Nathan Reed: Autor napisał, że ma scenę OpenGL, więc pomyślałem, że może to być także ten problem.
zacharmarz
Ten problem może wydawać się podobny lub powiązany, ale wartości bufora głębokości zdecydowanie nie są przechowywane w sposób zgodny z liczbami zmiennoprzecinkowymi. Jest to format stałego punktu. Z tego powodu odpowiedź może wprowadzać w błąd.
Steven Lu