Visualizador de padrões de bits de ponto flutuante IEEE 754

Decompõe um número decimal em seu padrão de bits IEEE 754 de precisão simples (32 bits) ou dupla (64 bits), ou decodifica um padrão de bits de volta para um valor decimal. Sinal, expoente e mantissa são exibidos com cores diferenciadas, com suporte para NaN, infinito e números subnormais.

O exemplo clássico: por que 0.1 + 0.2 não dá 0.3

No JavaScript, `0.1 + 0.2` resulta em `0.30000000000000004`, não `0.3`. Observar os padrões de bits em precisão dupla explica o motivo.

Valor Hexadecimal em precisão dupla (64 bits) Binário em precisão dupla (64 bits)
0.1 0x3FB999999999999A 0011111110111001100110011001100110011001100110011001100110011010
0.2 0x3FC999999999999A 0011111111001001100110011001100110011001100110011001100110011010
0.3 0x3FD3333333333333 0011111111010011001100110011001100110011001100110011001100110011
0.1 + 0.2 0x3FD3333333333334 0011111111010011001100110011001100110011001100110011001100110100

0.1, 0.2 e 0.3 são todas frações infinitamente periódicas em binário, então nenhuma delas cabe exatamente na mantissa de 52 bits e cada uma é arredondada. Como os erros de arredondamento de 0.1 e 0.2 não chegam a se cancelar, a soma acaba ficando apenas um bit diferente do padrão exato de 0.3 (0x3FD3333333333334). Tente inserir esses valores no conversor acima e compare você mesmo os bits finais.

Dicas

  • Converta 0.1, 0.2 e 0.3 para bits em precisão dupla e compare com o resultado de 0.1+0.2 (0.30000000000000004) — dá para perceber visualmente que só o último bit é diferente.
  • Digitar "-0" no campo decimal produz um padrão de bits com apenas o bit de sinal ativado (diferente de "+0"). Como números, 0 === -0, mas as representações em bits são distintas.
  • Quando o campo do expoente é todo zero, uma mantissa igual a zero significa zero, e uma mantissa diferente de zero significa um número subnormal — uma representação especial usada para alcançar valores extremamente próximos de zero à custa da precisão.
  • Alternar entre precisão simples (32 bits) e dupla (64 bits) para o mesmo valor, como 0.1, mostra como o erro de arredondamento é maior na precisão simples.
  • Para decodificar um padrão de bits de volta a um valor decimal, digite uma string hexadecimal com o prefixo `0x`, ou uma string binária com exatamente tantos dígitos quanto a precisão selecionada (o prefixo `0b` é opcional).

Perguntas frequentes

0.1, 0.2 e 0.3 são todas frações infinitamente periódicas em binário, então um computador precisa arredondar cada uma para caber na mantissa finita (52 bits em precisão dupla). Como os erros de arredondamento de 0.1 e 0.2 não chegam a se cancelar, a soma acaba levemente diferente do padrão de bits exato de 0.3 (0.30000000000000004). Isso não é um bug de uma linguagem específica — acontece em qualquer linguagem compatível com IEEE 754.

A precisão simples (float, 32 bits) usa 1 bit de sinal, 8 bits de expoente e 23 bits de mantissa. A precisão dupla (double, 64 bits) usa 1 bit de sinal, 11 bits de expoente e 52 bits de mantissa. Mais bits de mantissa dão mais dígitos significativos (precisão), e mais bits de expoente dão um intervalo representável mais amplo. A maioria das linguagens de programação usa precisão dupla por padrão para seu tipo de ponto flutuante.

É um padrão de bits em que o campo do expoente é todo zero, mas a mantissa é diferente de zero. Um número de ponto flutuante normal tem um 1 implícito no início da mantissa, mas um número subnormal omite esse 1 e, em vez disso, fixa o expoente em seu valor mínimo, permitindo representar valores ainda mais próximos de zero do que o menor número normal consegue — à custa de alguma precisão.

O IEEE 754 tem um bit de sinal independente, então mesmo quando o expoente e a mantissa são todos zero, um bit de sinal igual a 0 dá "+0" e um bit de sinal igual a 1 dá "-0" — dois padrões de bits distintos. Numericamente, 0 === -0 é verdadeiro, mas algumas operações revelam a diferença, como 1/0 produzir +Infinity enquanto 1/-0 produz -Infinity.

Ambos usam um padrão de bits em que o campo do expoente é todo um. Se a mantissa é toda zero, representa infinito (com o bit de sinal distinguindo +Infinity de -Infinity); se a mantissa é diferente de zero, representa NaN (Not a Number, não é um número). Eles surgem de operações matematicamente indefinidas, como divisão por zero ou ∞ - ∞.
ツールくん

Curiosidade — Como o IEEE 754 se tornou uma língua comum

Antes de 1985, cada fabricante de computadores usava sua própria representação de ponto flutuante. IBM, DEC, Cray e outros podiam calcular o mesmo valor, digamos "0.1", e obter comportamentos de arredondamento e intervalos representáveis sutilmente diferentes, de modo que portar um programa numérico de uma máquina para outra costumava produzir resultados ligeiramente diferentes. Para resolver esse caos, um grupo de trabalho liderado por William Kahan, da UC Berkeley — que também havia trabalhado no projeto do coprocessador numérico Intel 8087 —, impulsionou a criação de um padrão para toda a indústria, publicado em 1985 como IEEE 754.

A ideia central do IEEE 754 é dividir um número finito de bits (32 ou 64) em três papéis — sinal, expoente e mantissa — para aproximar o conjunto infinito dos números reais. O expoente cuida da escala (quão grande ou pequeno é um número), enquanto a mantissa cuida da precisão (quantos dígitos significativos ela tem), permitindo que um número fixo de bits cubra com flexibilidade tanto magnitudes muito pequenas quanto muito grandes. O expoente é armazenado somando-se um viés (127 na precisão simples, 1023 na dupla), de modo que comparar dois padrões de bits como simples inteiros sem sinal já basta para determinar qual valor é maior, evitando a complexidade extra de uma representação com sinal como o complemento de dois.

Somente frações cujo denominador é uma potência de dois (1/2, 1/4, 3/8 etc.) podem ser representadas exatamente em binário. O valor decimal 0.1 é a fração 1/10 e, como 10 não é uma potência de dois, ele se torna a fração binária infinitamente periódica 0.0001100110011... Com apenas um número finito de bits de mantissa disponíveis, é preciso truncar e arredondar em algum ponto — esta é a razão fundamental pela qual um computador não consegue representar 0.1 exatamente. É também por isso que sistemas contábeis que precisam de aritmética decimal exata costumam evitar números de ponto flutuante em favor da aritmética com inteiros ou de um tipo decimal dedicado (como BCD).

O IEEE 754 foi revisado depois em 2008 e 2019, adicionando precisão meia (16 bits), precisão quádrupla (128 bits) e formatos de ponto flutuante decimal, mas a divisão central em sinal, expoente e mantissa, junto com seus modos de arredondamento, permanece inalterada desde a edição original de 1985. Como praticamente todos os tipos float/double das linguagens de programação seguem esse padrão, a pergunta "por que 0.1 + 0.2 não é igual a 0.3?" aparece essencialmente da mesma forma, não importa qual linguagem você use.