Jaki jest największy „względny” poziom, jaki mogę uzyskać za pomocą float?

13

Tak jak pokazano to w grach takich jak oblężenie lochów i KSP, wystarczająco duży poziom zacznie mieć usterki z powodu działania zmiennoprzecinkowego. Nie możesz dodać 1e-20 do 1e20 bez utraty dokładności.

Jeśli zdecyduję się ograniczyć rozmiar mojego poziomu, jak obliczyć minimalną prędkość, z jaką mój obiekt może się poruszać, aż zacznie się chwiejne?

żart
źródło

Odpowiedzi:

26

32-bitowy float ma 23-bitową mantysę .

Oznacza to, że każda liczba jest reprezentowana jako 1.xxx xxx xxx xxx xxx xxx xxx xxx xx razy potęga 2, gdzie każda x jest cyfrą binarną, 0 lub 1. (Z wyjątkiem bardzo małych zdenormalizowanych liczb mniejszych niż - zaczynają się od 0. zamiast 1., ale zignoruję to dla poniższych)2126

Tak w zakresie od i , można reprezentować dowolną liczbę z dokładnością2i2(i+1)±2(i24)

Na przykład dla najmniejszą liczbą w tym zakresie jest . Kolejna najmniejsza liczba to . Jeśli chcesz reprezentować , musisz zaokrąglić w górę lub w dół, dla błędu obu przypadkach.i=0(20)1=1(20)(1+223)1+224224

In this range:                You get accuracy within:
-----------------------------------------------
         0.25   -     0.5    2^-26 = 1.490 116 119 384 77 E-08
         0.5    -     1      2^-25 = 2.980 232 238 769 53 E-08
         1     -      2      2^-24 = 5.960 464 477 539 06 E-08
         2     -      4      2^-23 = 1.192 092 895 507 81 E-07
         4     -      8      2^-22 = 2.384 185 791 015 62 E-07
         8     -     16      2^-21 = 4.768 371 582 031 25 E-07
        16     -     32      2^-20 = 9.536 743 164 062 5  E-07
        32     -     64      2^-19 = 1.907 348 632 812 5  E-06
        64     -    128      2^-18 = 0.000 003 814 697 265 625
       128    -     256      2^-17 = 0.000 007 629 394 531 25
       256    -     512      2^-16 = 0.000 015 258 789 062 5
       512    -   1 024      2^-15 = 0.000 030 517 578 125
     1 024    -   2 048      2^-14 = 0.000 061 035 156 25
     2 048    -   4 096      2^-13 = 0.000 122 070 312 5
     4 096    -   8 192      2^-12 = 0.000 244 140 625
     8 192   -   16 384      2^-11 = 0.000 488 281 25
    16 384   -   32 768      2^-10 = 0.000 976 562 5
    32 768   -   65 536      2^-9  = 0.001 953 125
    65 536   -  131 072      2^-8  = 0.003 906 25
   131 072   -  262 144      2^-7  = 0.007 812 5
   262 144   -  524 288      2^-6  = 0.015 625
   524 288 -  1 048 576      2^-5  = 0.031 25
 1 048 576 -  2 097 152      2^-4  = 0.062 5
 2 097 152 -  4 194 304      2^-3  = 0.125
 4 194 304 -  8 388 608      2^-2  = 0.25
 8 388 608 - 16 777 216      2^-1  = 0.5
16 777 216 - 33 554 432      2^0   = 1

Więc jeśli twoje jednostki są w metrach, stracisz milimetrową precyzję wokół pasma 16 484 - 32 768 (około 16-33 km od miejsca pochodzenia).

Powszechnie uważa się, że można obejść ten problem przy użyciu innej jednostki bazowej, ale nie jest to prawdą, ponieważ liczy się względna precyzja.

  • Jeśli użyjemy centymetrów jako jednostki, tracimy milimetrową precyzję w paśmie 1 048 576-2 097 152 (10-21 km od miejsca pochodzenia)

  • Jeśli użyjemy hektametrów jako jednostki, tracimy milimetrową precyzję w paśmie 128-256 (13-26 km od źródła)

... więc zmiana jednostki na ponad cztery rzędy wielkości wciąż kończy się utratą milimetrowej precyzji gdzieś w zasięgu dziesiątek kilometrów. Zmieniamy tylko to, gdzie dokładnie w tym paśmie uderza (z powodu niedopasowania numeracji base-10 i base-2), nie rozszerzając drastycznie naszego obszaru gry.

Dokładność, na jaką niedokładność może tolerować twoja gra, zależy od szczegółów rozgrywki, symulacji fizyki, rozmiaru jednostki / odległości rysowania, rozdzielczości renderowania itp., Więc ustalenie dokładnego limitu jest trudne. Być może renderowanie wygląda dobrze 50 km od miejsca pochodzenia, ale pociski teleportują się przez ściany lub wrażliwy skrypt rozgrywki oszaleje. Lub może się okazać, że gra gra dobrze, ale wszystko ma ledwo wyczuwalne wibracje wynikające z niedokładności w transformacji aparatu.

Jeśli znasz poziom dokładności, którego potrzebujesz (powiedzmy, rozpiętość 0,01 jednostek odwzorowuje do około 1 piksela przy typowej odległości oglądania / interakcji, a każde mniejsze przesunięcie jest niewidoczne), możesz skorzystać z powyższej tabeli, aby znaleźć miejsce, w którym go stracisz dokładność i cofnij się o kilka rzędów wielkości dla bezpieczeństwa w przypadku operacji stratnych.

Ale jeśli w ogóle myślisz o dużych odległościach, lepiej może to wszystko ominąć, ponownie skupiając swój świat na ruchu gracza. Wybieramy konserwatywnie mały kwadratowy lub sześcianowy region wokół początku. Ilekroć gracz porusza się poza ten region, tłumacz go i wszystko na świecie, cofnij się o połowę szerokości tego regionu, trzymając gracza w środku. Ponieważ wszystko porusza się razem, Twój gracz nie zobaczy zmiany. Niedokładności mogą wciąż zdarzać się w odległych częściach świata, ale generalnie są one znacznie mniej zauważalne niż dzieje się tuż pod stopami, a masz gwarancję, że zawsze będziesz mieć wysoką precyzję w pobliżu gracza.

DMGregory
źródło
1
Recentering to zdecydowanie najlepsza droga!
Floris
2
Co z użyciem współrzędnych stałych punktów? Może w razie potrzeby z 64-bitowymi liczbami całkowitymi?
API-Beast
pytanie brzmi, jak duży może być ten ponownie wyśrodkowany region? jeśli na przykład w mojej grze chcę strzelać z dużej odległości przy silnym powiększeniu, to czy muszę bezwzględnie używać podwójnej odległości, czy jest wystarczająca liczba zmiennoprzecinkowa? czy nie lepiej jest wyśrodkować zgodnie z drzewem quadów lub jakimś algorytmem kafelkowym?
Jokoon
Będzie to zależeć od stabilności numerycznej algorytmów wykorzystywanych przez systemy renderowania i fizyki - więc dla danej bazy kodu / silnika jedynym sposobem, aby się upewnić, jest wypróbowanie sceny testowej. Możemy użyć tabeli do oszacowania maksymalnej dokładności (np. Kamera 16 km od obiektu będzie miała tendencję do zauważania błędów co najmniej milimetrowych, więc twój zoom powinien być wystarczająco szeroki, aby utrzymać te mniejsze niż piksel - jeśli zoom potrzebuje aby być bardziej restrykcyjnym w grze, może się podwoić lub może być wymagana sprytna matematyka), ale łańcuch stratnych operacji może napotkać problemy na długo przed tym hipotetycznym limitem.
DMGregory
Nadal zastanawiam się, co to znaczy wyśrodkować świat na graficzny interfejs API. Jeśli mam duży fragment geometrii instancji (lub nie instancji), czy nadal jest to bezpieczne? Chyba oznacza to tłumaczenie WSZYSTKICH transformacji, ale jeśli zrobię to kilka razy, czy nie istnieje ryzyko utraty precyzji zmiennoprzecinkowej?
Jokoon
3

Trudno odpowiedzieć, ponieważ zależy to od skali twojej fizyki: Jaka jest dopuszczalna minimalna prędkość ruchu, której NIE trzeba zaokrąglać do zera?

Jeśli potrzebujesz dużego świata i spójnej fizyki, lepiej użyć klasy punktów stałych.

Na przykład wystrzelenie kuli armatniej z dowolnego miejsca na świecie da ten sam wynik, a 64-bitowy punkt stały (32,32) zapewni ogromną precyzję i więcej niż cokolwiek zauważalnego w większości gier. Jeśli twoja jednostka ma 1 metr, nadal jesteś w odległości 232 pikometrów z dokładnością 2147483 km od źródła.

Nadal możesz wykonywać fizykę lokalną w zmiennoprzecinkowych komórkach lokalnych, aby zaoszczędzić na programowaniu i korzystać z gotowego silnika fizyki. Nadal będzie w miarę spójny we wszystkich praktycznych celach.

Jako dodatkowa faza szeroka i AABB wydają się być szybsze w ustalonym punkcie ze względu na opóźnienia FPU. Szybsze jest także przekształcanie stałego punktu na indeks oktawy (lub kwadratu), ponieważ można wykonać proste maskowanie bitów.

Operacje te nie korzystają tak bardzo z instrukcji SIMD i potokowania, które normalnie ukrywają opóźnienia FPU.

Możesz przekonwertować pozycje na zmiennoprzecinkowe PO odejmując pozycję kamery w stałym punkcie, aby renderować wszystko, unikając problemów ze zmiennoprzecinkowymi w dużym świecie i nadal używając zwykłego renderera używającego zmiennoprzecinkowych.

Stephane Hockenhull
źródło
-3

Można tego całkowicie uniknąć przez pomnożenie.
Zamiast pracować z liczbami zmiennoprzecinkowymi, wystarczy pomnożyć je przez 10 ^ (x), zapisać je, a w razie potrzeby pomnożyć ponownie przez 10 ^ (- x).
Od tego zależy, jakiego typu int chcesz użyć.

Zaraz
źródło
2
To nie omija problemu. Format punktu stałego nadal ma skończoną precyzję i zakres - im większy zakres, tym niższa precyzja (w zależności od tego, gdzie umieściłeś ten stały punkt dziesiętny) - więc decyzja „jak duży mogę zrobić poziom bez widocznych błędów zaokrąglania” nadal obowiązuje.
DMGregory
3
Co więcej, baza-10 nie jest bardzo praktyczna. Liczby stałoprzecinkowe działają znacznie lepiej, gdy dzielisz liczbę całkowitą pod względem bitów (rozważ bez znaku 26,6 - składową ułamkową jest dolna 6 bitów ( 1,0 / 64,0 * x i 63), a integralną częścią jest po prostu x >> 6) . Jest to o wiele prostsze do wdrożenia niż podniesienie czegoś do potęgi dziesięciu.
Andon M. Coleman