Float Exposed
(float.exposed)- Este texto explica como valores de ponto flutuante (float) são armazenados na memória e representados
- O foco está em como converter entre a forma hexadecimal e decimal do valor e o valor numérico real
- Explica a definição das áreas de sinal (Sign), expoente (Exponent) e significando (Significand) e o papel de cada uma
- Inclui exemplos de como interpretar exatamente quais valores binários e decimais um determinado valor
floatrepresenta - Também menciona o cálculo da diferença (Delta) entre valores representáveis
Análise da estrutura de armazenamento de valores de ponto flutuante
- Existem vários formatos de ponto flutuante, como
halfb float float double - Cada valor pode ser verificado na memória como Raw Hexadecimal Integer Value (valor inteiro hexadecimal bruto) e Raw Decimal Integer Value (valor inteiro decimal bruto)
- Os dados hexadecimais são conectados à notação real de ponto flutuante por meio da Hexadecimal Form ("%a")
- A posição de cada valor é apresentada como Significand–Exponent Range (posição no intervalo significando–expoente)
Como interpretar valores binários e decimais
- Um número de ponto flutuante pode ser expresso em Base-2 (expressão avaliada em binário) da seguinte forma:
- (−12)02×102(100010012 − 011111112)×1.011111110010100000000002
→ trata-se da avaliação numérica por meio de uma expressão binária
- (−12)02×102(100010012 − 011111112)×1.011111110010100000000002
- Em Base-10 (expressão avaliada em decimal), a forma é esta:
- 1×210×1.4967041015625
→ expresso como o produto de 2 elevado à 10ª potência e a parte fracionária
- 1×210×1.4967041015625
- O valor decimal exato obtido na conversão também é mostrado:
- apresentado em uma forma como 1.532625×103
Cálculo da distância (Delta) até valores vizinhos
- O Delta (intervalo) entre valores representáveis tem um significado importante
- São fornecidas separadamente as distâncias até o próximo e o valor representável anterior (Delta to Next/Previous Representable Value)
- Exemplo: ±1.220703125×10-4
- Esse intervalo está relacionado aos algarismos significativos / à precisão do valor de ponto flutuante
Resumo
- A representação na memória de números de ponto flutuante e o princípio de conversão para binário e decimal
- Explicação da estrutura sign, exponent, significand
- Também organiza informações sobre o intervalo de representação e a distância até valores adjacentes
1 comentários
Opiniões do Hacker News
Sobre este tema, esta é a melhor explicação: https://fabiensanglard.net/floating_point_visually_explained/ Encontrei este texto quando comecei a acompanhar o Hacker News, e ele me deu motivação para continuar na plataforma por causa de conteúdos assim: https://news.ycombinator.com/item?id=29368529
Talvez eu seja enviesado demais para o lado matemático, mas essa explicação não me pareceu tão fácil assim Se você quer uma explicação realmente simples sobre ponto flutuante: ele fornece aproximadamente a mesma quantidade de precisão em bits, independentemente da escala Ou seja, seja para números muito menores que 1, próximos de 1 ou muito grandes, você pode esperar quase o mesmo nível de precisão nos bits mais significativos Essa é a propriedade central, mas não é fácil internalizá-la
Combina muito bem com o contexto do blog escrito recentemente pela equipe da TM https://news.ycombinator.com/item?id=45200925
Nunca tinha visto isso explicado tão bem, então agradeço por compartilhar
Um dos problemas em que pensei bastante foi “como representar um valor
floatcomo a string decimal mais curta e, ainda assim, sem ambiguidades” Por exemplo, ao usarfloatde precisão simples, são necessários até 9 dígitos de precisão decimal para identificar ofloatde forma única Por isso é preciso usar um padrão deprintfcomo%.9gMas, nesse caso,0.1acaba sendo impresso como algo feio tipo0.100000001Então normalmente se arredonda para 6 dígitos, e com%.6gum valor decimal digitado com até 6 dígitos pode ser exibido igual ao valor armazenado Mas, para valores resultantes de cálculos, isso deixa de ser seguro para round-trip Isso importa especialmente quando é preciso comparar valoresfloatcom exatidão (por exemplo, para verificar se dados mudaram) A ideia que tive foi: primeiro imprimir com 6 dígitos e, se ao fazer o parse o mesmo valor binário sair de volta, usar isso; se não, repetir com 7, 8 e 9 dígitos até encontrar a representação decimal mais curta Meu algoritmo é o seguinteFico pensando se existe uma forma mais eficiente de encontrar a representação mais curta sem repetir
printf/scanfEsse problema é realmente importante Dá para vê-lo como o problema de produzir uma string “normalizada” para um
floatespecífico Por isso existem vários algoritmos eficientes, como Dragon4, Grisu3, Ryu e Dragonbox A biblioteca double-conversion do Google também implementa os dois primeirosHá uma forma melhor de obter isso sem um loop de
printf/scanfDá até para fazer só comprintf("%f", ...)O algoritmo real de conversão defloatpara string é bem complexo Um algoritmo bom e recente é https://github.com/ulfjack/ryu Acho que surgiu recentemente um método ainda mais eficiente, mas não lembro o nomeNão precisa se preocupar tanto com as opiniões negativas; embora talvez não seja o melhor método, se não houver erros, geralmente funciona bem o suficiente Já passei por algo parecido: certa vez eu queria encontrar um vetor que, após uma rotação de Euler
(5°, 5°, 0), resultasse no mesmo vetor, então fui deslocando vetores aleatoriamente e vendo se se aproximavam mais do vetor de referência Rodei um loop de vários milhões de iterações e consegui o resultado em poucos segundos no Python Em nível de biblioteca seria ineficiente, mas para o meu caso de uso foi mais do que satisfatórioVale a pena ver
std::numeric_limits<float>::max_digits10https://en.cppreference.com/w/cpp/types/numeric_limits/max_digits10.htmlIsso não faz sentido, e
sscanf()jamais deveria ser usado Converta para inteiro sem sinal e serialize/restaure assim, que é reversível sem perda de informaçãoSe precisar de uma representação mais curta, use uma heurística com restauração exata circular, desde que o método garanta a precisão original (por exemplo, idempotência)
Minha dica favorita sobre FP é que comparação de
floatquase pode ser usada como comparação de inteiros Para decidira > b, basta interpretaraebcomo inteiros com sinal e comparar normalmente Isso funciona (quase sempre) Ou seja, o próximo valorfloatmaior é simplesmente o padrão de bits reinterpretado como inteiro e incrementado em 1 Por exemplo, se você começar de0.0emfloate somar 1 como inteiro, esse já é o próximo valorfloat(denormal, o menor espacinho possível) É assim quenextaftertambém é implementado Saber que os valores defloatseguem a mesma ordem de comparação dos inteiros deixa tudo muito mais intuitivo Claro, há exceções:NaN, infinito,-0etc. são diferentes Isso é útil em alguns casos, mas não em todosIsso, estritamente falando, não é verdade Vale para positivos ou para comparações entre positivo e negativo, mas entre negativos é diferente Ponto flutuante padrão (
float) usa sign-magnitude, enquanto inteiros com sinal modernos usam complemento de dois Entre negativos, a direção da comparação de magnitude se inverte Se você incrementar umfloatcomo se fosse umint, normalmente vai para um número de “magnitude” maior dentro do mesmo sinal Ou seja, positivos sobem, e negativos descem para números negativos ainda menores Já inteiros sempre sobem ou estouram em overflow Uma forma mais precisa de dizer isso é que a comparação equivale à de inteiros sign-magnitude Claro, os caveats mencionados continuam valendoA propósito, o algoritmo de ordenação total para ponto flutuante do Rust, em que até
NaNpode ser comparado, é o seguinte (recomendado pela IEEE 751)Ver o algoritmo completo
Vi esse ponto num caso do meu curso de Game AI no OMSCS, sobre cuidados ao representar a posição de objetos do jogo com ponto flutuante Quanto mais longe da origem ou do ponto de referência, mais perigoso fica, porque o
floatvai gastando precisão para armazenar valores maioresÉ interessante como esse fenômeno virou algo mítico no Minecraft com as Far Lands Ou seja, quanto mais longe você vai da origem do mundo, mais a geração de terreno e a física começam a ficar estranhas, até que muito mais longe tudo quebra de vez Tem até um certo ar ocultista, como se as leis da realidade fossem se desfazendo aos poucos E tudo isso por causa dos limites de precisão de
floatAo somar muitos números entre 0 e 1 em
float, comparar uma soma ingênua em sequência com uma soma em pares, somando de dois em dois e depois reunindo os resultados, mostra que o método em pares é muito mais preciso É um exemplo de como o erro acumulado emfloatpode ser sério Houve casos reais em que esse tipo de erro foi ignorado e causou problemas Donald Knuth explica esses pontos e verdades básicas sobrefloat, comoa + (b + c) ≠ (a + b) + c, em "The Art of Computer Programming" Também houve situações reais em que isso causou problemas: o sistema de mísseis Patriot acumulava tempo emfloat, o erro ia crescendo e acabava desviando completamente do alvo, exigindo reinicialização Era preciso reiniciar a cada 24 horas, e o software do sistema acabou sendo corrigido Também já houve casos de grandes estruturas desabarem por causa de erro defloat(quando uma medida de espessura foi calculada fina demais)Você precisa definir primeiro as condições de contorno para estabelecer quanta precisão é necessária A partir disso, dá para calcular antes as distâncias mínima e máxima Se o mundo ficar grande demais, é preciso dividi-lo em setores ou gerenciar separadamente coordenadas globais/locais (por exemplo, em No Man's Sky) Jogos são, no fim das contas, um tipo de truque de palco
Double-Precisionjá é suficiente para a maioria dos casos O importante é lembrar de não somar valores pequenos com valores grandes ao mesmo tempoKerbal Space Program usa uma engenharia bem inteligente para tentar representar um sistema solar inteiro só com
floatde 32 bits Há muitos artigos e vídeos sobre isso, e recomendo bastanteEssa visualização é divertida e me chamou atenção por ser parecida, visualmente, com a calculadora de intervalos CIDR que fiz há algum tempo para ajudar a entender faixas de rede Visualizações assim são muito úteis
Antigamente, quando eu explorava representações de
float, usava https://www.h-schmidt.net/FloatConverter/IEEE754.html O bom desse site é que ele também mostra o erro de conversão, mas não oferece suporte a double precisionfloat, isso pode parecer óbvio, mas para quem está aprendendo pela primeira vez, é um ponto que pede explicação adicionalAinda não vi este comentário compartilhar isso, mas meu site favorito sobre
floaté https://0.30000000000000004.com/No
floatde 32 bits, o “inteiro mais interessante” é 16777217 (e no de 64 bits, 9007199254740992) É divertido conhecer esses edge cases para testesEm
floatde 64 bits,9007199254740991éNumber.MAX_SAFE_INTEGERno JavaScript Esse valor não é par, e o próximo valor,9007199254740992, também é seguro por si só, mas valores claramente inseguros como9007199254740993acabam arredondados e deixam de poder ser distinguidosEm
floatde 64 bits, é exatamente±9,007,199,254,740,993.0:-) A propósito, esses valores representam o primeiro número logo após o limite do maior inteiro quefloatconsegue representar “exatamente” Por exemplo, nofloatde 32 bits, o próximo valor representável depois de±16,777,216.0é±16,777,218.0±16,777,217.0não pode ser representado e normalmente é arredondado em direção ao zero ou algo do tipo Esse tipo de limite de precisão e problema de arredondamento muitas vezes passa despercebidoFico feliz que a IEEE754 exista, mas não acho que ela seja perfeita, e acho que valores como posit seriam melhores (assumindo ausência de suporte em hardware) BigNum rational (racionais) é ainda superior aos dois, mas também é o mais lento
Seria muito legal se isso passasse a oferecer suporte aos vários formatos
fp8introduzidos recentemente nas GPUs