Por que a resolução dos números de ponto flutuante diminui ainda mais a partir de uma origem?

19

Minha cena do OpenGL possui objetos que são posicionados a distâncias ridiculamente distantes da origem. Quando visualizo esses objetos e giro / giro / zoom uma câmera ao redor deles, eles 'tremem'. Ou seja, os vértices que compõem os objetos parecem se encaixar em uma grade 3d imaginária de pontos. Eu li que esse é um problema comum devido à quantidade de informações que podem ser armazenadas usando a precisão do ponto flutuante (que o OpenGL e praticamente todo o resto usa). Eu não entendo por que isso acontece embora.

Ao procurar uma solução, deparei-me com a correção muito simples de 'origem flutuante', e ela parece funcionar. Eu apenas transformo tudo para que meus objetos estejam nas mesmas posições relativas, mas o que quer que minha câmera esteja perto da origem. Encontrei uma explicação aqui: http://floatingorigin.com/ , mas não consegui segui-la.

Então ... Alguém poderia explicar por que posicionar minha cena muito longe (digamos, 10 milhões de unidades) da origem resulta no comportamento errático que observei? E também por que movê-lo para perto da origem resolve o problema?

Pris
fonte
4
Porque se não o fizessem, seriam números de ponto fixo . Questão tautológica, isso.
MSalters
11
É verdade, mas somente quando você entende o que significa "ponto flutuante".
Kylotan

Respostas:

26

Isso é tudo devido à maneira como os pontos flutuantes são representados nos computadores.

Os números inteiros são armazenados de maneira bastante direta; cada unidade é exatamente "uma", além da "anterior", como seria de esperar com números contáveis.

Com números de ponto flutuante, esse não é exatamente o caso. Em vez disso, vários bits indicam o EXPONENTE, e o restante indica o que é conhecido como mantissa , ou parte fracionária que é então MULTIPLICADA pela parte do expoente (implicitamente 2 ^ exp) para fornecer o resultado final.

Procure aqui uma explicação visual dos bits.

É precisamente por esse expoente ser uma parte real dos bits que a precisão começa a diminuir quando os números aumentam.

Para ver isso em ação, vamos fazer uma representação de ponto flutuante falso sem entrar no âmago da questão: pegue um pequeno expoente como 2 e faça algumas partes fracionárias para testar:

2 * 2 ^ 2 = 8

3 * 2 ^ 2 = 12

4 * 2 ^ 2 = 16

... etc

Esses números não crescem muito separados apenas no expoente 2. Mas agora vamos tentar o expoente 38:

2 * 2 ^ 38 = 549755813888

3 * 2 ^ 38 = 824633720832

4 * 2 ^ 38 = 1099511627776

Whoa, enorme diferença agora!

O exemplo, embora não vá especificamente para MUITO PRÓXIMO CONTÁBIL (que seria a próxima parte fracionária dependendo de quantos bits são), existe para demonstrar a perda de precisão quando os números aumentam. A unidade "próximo contável" em carros alegóricos é muito pequena com expoentes pequenos e MUITO grande com expoentes maiores, enquanto em números inteiros é SEMPRE 1.

A razão pela qual o método de origem de flutuação funciona é porque ele está dimensionando todos esses números de ponto flutuante de expoente potencialmente grande PARA BAIXO PARA expoente pequeno para que os "próximos contáveis" (precisão) possam ser muito pequenos e felizes.


fonte
Os exemplos que você deu foram muito ilustrativo, graças :)
Pris
3
No caminho certo, mas eu gostaria que você tivesse usado exemplos que estão mais próximos da maneira como o ponto flutuante realmente funciona. Não eleva a mantissa ao expoente; é mantissa * 2 ^ expoente.
Nathan Reed
3
Você está certo, eu sabia disso; Não sei o que estava pensando. Editou minha resposta.
11
@ScottW Nice edit! 1
Nathan Reed
17

Como os números de ponto flutuante são representados como fração + expoente + sinal, e você só tem uma quantidade fixa de bits para a parte da fração.

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

À medida que você obtém números cada vez maiores, você simplesmente não possui os bits para representar as partes menores.

Tetrad
fonte
8

O clássico no campo deve ser mencionado: o que todo cientista da computação deve saber sobre números de ponto flutuante .

Mas a essência disso tem a ver com como os números de ponto flutuante de precisão simples (dupla) são apenas um número binário de 32 bits (64 bits), com 1 bit representando o sinal, um expoente de 8 bits (11 bits) da base 2 e um significando de 23 bits (52 bits) (os parênteses são os valores para o dobro).

Isso significa que o menor número positivo que você pode representar com precisão única é 0,0000000000000000000001 x 2 -127 = 2 -22 x 2 -127 = 2 -149 ~ 1,40 x 10 -45 .

O próximo número positivo é o dobro que: 0,0000000000000000000010 x 2 -127 = 2 -148 ~ 2,80 x 10 -45 e, em seguida, o próximo número é a soma dos dois anteriores 0,00000000000000000000000011 x 2 -127 = 3 x 2 -149 ~ 4.2 - 45 .

Isso continua aumentando pela mesma diferença constante até: 0,11111111111111111111111 x 2 -127 = 2 -126 - 2 149 ~ 1,117549435 x 10 -38 - 0,00000014 x 10 -38 = 1,117549421 x 10 -38

Agora você alcançou os números normais (onde o primeiro dígito no significando é 1) especificamente: 1.00000000000000000000000000 x 2 -126 = 2 -126 = 1.17549435 x 10 -38 e o próximo número é então 1.000000000000000000000000 x 2 -126 = 2 -126 (1 + 2 -22 ) = 1,17549435 x 1,00000023.

dr jimbob
fonte
2

A razão pela qual os números de ponto flutuante se tornam menos precisos ainda mais a partir da origem é porque um número de ponto flutuante deve ser capaz de representar números grandes. A maneira como isso é feito empresta o termo "ponto flutuante". Ele divide os possíveis valores que pode ser obtido (que é determinado pelo comprimento de bits) para que exista aproximadamente o mesmo número para cada expoente: para um flutuador de 32 bits, 23 dos bits definem a mantissa ou significando. Portanto, ele poderá assumir o valor de 2 ^ 23 valores diferentes em cada intervalo de expoente. Um desses intervalos de expoente é 1-2 [2 ^ 0 a 2 ^ 1], portanto, dividir o intervalo de 1 a 2 em 2 ^ 23 valores diferentes permite muita precisão.

Mas dividir o intervalo [2 ^ 10 a 2 ^ 11] em 2 ^ 23 valores diferentes significa que o espaço entre cada valor é muito maior. Se não fosse, então 23 bits não seriam suficientes. A coisa toda é um compromisso: você precisa de um número infinito de bits para representar qualquer número real. Se seu aplicativo funcionar de uma maneira que permita uma precisão menor para valores maiores, e você se beneficiar de poder realmente representar valores grandes , use uma representação de ponto flutuante.

Steven Lu
fonte
apenas fazendo uma anotação aqui após uma revisão mais aprofundada, sete anos depois ... meus números em meus exemplos não são particularmente bem escolhidos. Mas os pontos gerais são válidos.
Steven Lu
1

Pode ser um pouco difícil oferecer exemplos específicos de como a precisão de ponto flutuante funciona. Para complementar as outras respostas, aqui está uma. Digamos que tenhamos um número decimal de ponto flutuante, com três dígitos de mantissa e um dígito de expoente:

mantissa × 10 expoente

Quando o expoente é 0, todos os números inteiros no intervalo de 0 a 999 podem ser representados com precisão. Quando é 1, você está basicamente multiplicando cada elemento desse intervalo por 10, para obter o intervalo de 0 a 9990; mas agora, apenas múltiplos de 10 podem ser representados com precisão, porque você ainda tem apenas três dígitos de precisão. Quando o expoente atinge o máximo de 9, a diferença entre cada par de números inteiros representáveis ​​é de um bilhão . Você está literalmente trocando precisão por alcance.

Funciona da mesma maneira com números binários de ponto flutuante: sempre que o expoente aumenta um, o intervalo dobra , mas o número de valores representáveis ​​nesse intervalo é reduzido pela metade . Isso também se aplica a números fracionários, que é obviamente a fonte do seu problema.

Jon Purdy
fonte
0

Em geral, a resolução piora porque é multiplicada pelo valor do expoente (2 ** parte do expoente).

em reconhecimento ao comentário de Josué: o acima foi apenas para colocar a resposta em uma declaração sucinta. Obviamente, como tentei indicar em http://floatingorigin.com/ , isso está apenas começando com uma solução geral e seu programa pode ter tremores de vários lugares: no pipeline de precisão ou em outras partes do código .

Chris Thorne
fonte
Isso não adiciona nada que ainda não esteja presente em outras respostas.
Verdade: percebi que poderia descrever a resposta em uma única linha e pensei que alguém poderia achar útil uma resposta sucinta.
Chris Thorne
-1

O buffer de profundidade do OpenGL não é linear . Quanto mais longe você for, pior será a resolução. Eu recomendo ler isso . Algo retirado de lá (12.070):

Em resumo, a divisão da perspectiva, por sua natureza, causa mais precisão de Z próximo à frente do volume da visualização do que na parte traseira.

E outro (12.040):

Você pode ter configurado seus planos de recorte zNear e zFar de maneira a limitar severamente a precisão do buffer de profundidade. Geralmente, isso é causado por um valor do plano de recorte zNear muito próximo de 0,0. À medida que o plano de recorte zNear é ajustado cada vez mais perto de 0,0, a precisão efetiva do buffer de profundidade diminui drasticamente. Mover o plano de recorte zFar para mais longe sempre tem um impacto negativo na precisão do buffer de profundidade, mas não é tão dramático quanto mover o plano de recorte zNear.

Portanto, mova o plano de recorte próximo o mais longe possível e o plano mais distante o mais próximo possível.

zacharmarz
fonte
-1: a questão é sobre precisão de ponto flutuante, não os problemas de precisão com a representação do buffer de profundidade não linear.
Nathan Reed
É possível que o que estou vendo seja devido a problemas de buffer de profundidade. Estou usando uma biblioteca em cima do OpenGL para visualizar minha cena e supondo que ele configure a câmera, a visão e os planos de recorte próximo e distante para explicar o tamanho e a posição da geometria (desde o visualizador A ferramenta parece definir automaticamente uma visualização ideal para o conteúdo da cena). Mas acho que isso pode não ser o caso - vou tentar brincar com os planos de recorte deixando a posição original intacta e ver o que acontece.
Pris
2Nathan Reed: O autor escreveu que ele tem a cena do OpenGL, então eu pensei que poderia ser esse problema também.
Zacharmarz
Esse problema pode parecer semelhante ou relacionado, mas definitivamente os valores do buffer de profundidade NÃO são armazenados de maneira compatível com números de ponto flutuante. É um formato de ponto fixo. É por isso que a resposta pode ser enganosa.
Steven Lu