Qual è il livello "relativo" più grande che posso fare usando float?


13

Proprio come è stato dimostrato con giochi come Dungeon Siege e KSP, un livello abbastanza grande inizierà ad avere problemi a causa del funzionamento in virgola mobile. Non è possibile aggiungere 1e-20 a 1e20 senza perdere la precisione.

Se scelgo di limitare la dimensione del mio livello, come posso calcolare la velocità minima a cui il mio oggetto può muoversi fino a quando non inizia a diventare instabile?

Risposte:


26

Un float a 32 bit ha una mantissa a 23 bit .

Ciò significa che ogni numero è rappresentato come 1.xxx xxx xxx xxx xxx xxx xxx xx volte una potenza di 2, dove ogni x è una cifra binaria, 0 o 1. (Ad eccezione di numeri denormalizzati estremamente piccoli inferiori a 2-126 - iniziano con 0. anziché 1., ma li ignorerò per quanto segue)

Pertanto, nell'intervallo compreso tra 2io e 2(io+1) , è possibile rappresentare qualsiasi numero con una precisione di ±2(io-24)

Ad esempio, per io=0 , il numero più piccolo in questo intervallo è (20)1=1 . Il numero più piccolo successivo è (20)(1+2-23) . Se si desidera rappresentare 1+2-24 , è necessario arrotondare per eccesso o per difetto, per un errore di 2-24 entrambi i modi.

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

Quindi, se le tue unità sono metri, perderai la precisione millimetrica attorno alla banda 16 484 - 32 768 (circa 16-33 km dall'origine).

Si ritiene comunemente che si possa aggirare questo problema utilizzando un'unità di base diversa, ma non è proprio vero, poiché è la precisione relativa che conta.

  • Se usiamo centimetri come nostra unità, perdiamo la precisione millimetrica nella banda 1 048 576-2 097 152 (10-21 km dall'origine)

  • Se usiamo ettometri come nostra unità, perdiamo la precisione millimetrica nella banda 128-256 (13-26 km dall'origine)

... quindi cambiare l'unità su quattro ordini di grandezza finisce ancora con una perdita di precisione millimetrica da qualche parte nel raggio di decine di chilometri. Tutto ciò che stiamo spostando è dove esattamente in quella banda colpisce (a causa della discrepanza tra la numerazione base-10 e base-2) non estendendo drasticamente la nostra area giocabile.

La precisione esatta che il tuo gioco può tollerare dipenderà dai dettagli del tuo gameplay, dalla simulazione fisica, dalle dimensioni delle entità / distanze di disegno, dalla risoluzione di rendering, ecc. Quindi è difficile stabilire un limite preciso. Forse il tuo rendering sembra a 50 km dall'origine, ma i tuoi proiettili si teletrasportano attraverso i muri o uno script di gameplay sensibile va in tilt. Oppure potresti trovare che il gioco funzioni bene, ma tutto ha una vibrazione appena percettibile a causa di imprecisioni nella trasformazione della videocamera.

Se conosci il livello di precisione di cui hai bisogno (ad esempio, un intervallo di 0,01 unità si mappa a circa 1 px alla tua distanza di visualizzazione / interazione tipica e qualsiasi offset più piccolo è invisibile), puoi utilizzare la tabella sopra per trovare dove lo perdi precisione e fare un passo indietro di alcuni ordini di grandezza per la sicurezza in caso di operazioni in perdita.

Ma se stai pensando a grandi distanze, potrebbe essere meglio eludere tutto ciò, aggiornando il tuo mondo mentre il giocatore si muove. Scegli una regione di forma quadrata o cubica conservativamente piccola attorno all'origine. Ogni volta che il giocatore si sposta al di fuori di questa regione, traducili e tutto il mondo, indietro di metà della larghezza di questa regione, mantenendo il giocatore dentro. Poiché tutto si muove insieme, il tuo giocatore non vedrà alcun cambiamento. Le imprecisioni possono ancora accadere in parti distanti del mondo, ma in genere sono molto meno evidenti lì che accadono proprio sotto i tuoi piedi, e hai la garanzia di avere sempre un'alta precisione disponibile vicino al giocatore.


1
Il recente è sicuramente la strada da percorrere!
Floris,

2
Che dire dell'utilizzo di coordinate in punti fissi? Forse con numeri interi a 64 bit, se necessario?
API-Beast,

la domanda è: quanto può essere grande quella regione ricentrata? se per esempio nel mio gioco voglio sparare ad alta distanza con uno zoom forte, devo assolutamente usare il doppio o è abbastanza galleggiante? non è meglio per i più recenti secondo un albero quad o un algoritmo a tessere?
jokoon

Dipenderà dalla stabilità numerica degli algoritmi impiegati dal tuo rendering e dai tuoi sistemi fisici - quindi per un dato codice / motore, l'unico modo per sapere con certezza è provare una scena di prova. Possiamo usare la tabella per stimare la massima precisione (ad es. Una telecamera a 16 km di distanza da un oggetto tenderà a vedere errori di almeno un millimetro, quindi lo zoom dovrebbe essere abbastanza largo da tenerli più piccoli di un pixel - se lo zoom ha bisogno per essere più rigoroso per il tuo gioco, allora potrebbe essere necessario raddoppiare o potrebbe essere necessaria una matematica intelligente), ma una catena di operazioni in perdita può incorrere in problemi ben prima di questo ipotetico limite.
DMGregory

Mi chiedo ancora cosa significhi per il mondo più recente un'API grafica. Se ho un grosso pezzo di geometria istanziata (o non istanziata), è ancora sicuro farlo? Immagino significhi tradurre TUTTE le trasformazioni, ma se lo faccio più volte, non c'è il rischio di perdita di precisione in virgola mobile?
jokoon

3

È difficile rispondere poiché dipende dalla scala della tua fisica: qual è la velocità di movimento minima accettabile che NON deve essere arrotondata a zero?

Se hai bisogno di un mondo vasto e di una fisica coerente, è meglio usare una classe di punti fissi.

Ad esempio, sparare una palla di cannone da qualsiasi parte del mondo ti darà lo stesso risultato e un punto fisso a 64 bit (32.32) ti darà una grande quantità di precisione e più di ogni altra cosa percepibile nella maggior parte dei giochi. Se la tua unità è di 1 metro sei ancora a 232 picometri di precisione a 2147483 km di distanza dall'origine.

È ancora possibile eseguire la fisica locale in punti mobili all'interno della cella locale per risparmiare sul lavoro di programmazione e utilizzare un motore fisico standard. Sarà comunque ragionevolmente coerente per tutti gli scopi pratici.

Come bonus fase ampia e AABB tendono ad essere più veloci in virgola fissa a causa della latenza FPU. È anche più veloce convertire un punto fisso in un indice octree (o quadtree) in quanto è possibile eseguire un semplice mascheramento dei bit.

Tali operazioni non beneficiano tanto delle istruzioni SIMD e del pipelining che normalmente nascondono la latenza della FPU.

È possibile convertire le posizioni in virgola mobile DOPO aver sottratto la posizione della telecamera in un punto fisso per eseguire il rendering di tutto, evitando problemi di virgola mobile in un mondo di grandi dimensioni e ancora utilizzando un renderer regolare utilizzando punti in virgola mobile.


-3

Puoi evitarlo del tutto per moltiplicazione.
Invece di lavorare con i float, è sufficiente moltiplicarli per 10 ^ (x), memorizzarli e, quando necessario, moltiplicarli di nuovo per 10 ^ (- x).
Da ciò dipende dal tipo di int che si desidera utilizzare.


2
Questo non evita il problema. Un formato a virgola fissa ha ancora precisione e intervallo finiti - maggiore è l'intervallo, minore è la precisione (in base a dove si posiziona quel punto decimale fisso) - quindi la decisione di "quanto grande posso fare un livello senza errori di arrotondamento visibili" si applica ancora.
DMGregory

3
Inoltre, la base-10 non è molto pratica. I numeri a virgola fissa funzionano molto meglio quando si divide il numero intero in termini di bit (considerare 26.6 senza segno - il componente frazionario è il 6 bit inferiore ( 1.0 / 64.0 * x & 63) e la parte integrale è semplicemente x >> 6) . È molto più semplice da implementare che elevare qualcosa a una potenza di dieci.
Andon M. Coleman,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.