Visualizador flutuante IEEE-754
Veja exatamente como um número decimal é armazenado como um float IEEE-754 – bit de sinal, bits de expoente e bits de mantissa – e por que números como 0,1 não são armazenados exatamente.
Resultado
Cada número de ponto flutuante que um computador armazena - um número JavaScript, um float C, um float Python - é codificado da mesma maneira no IEEE 754: um bit de sinal, um bloco de bits de expoente e um bloco de bits de mantissa (fração). A precisão única (32 bits) usa 1 bit de sinal, 8 bits de expoente e 23 bits de mantissa; precisão dupla (64 bits), o formato por trás do tipo de número padrão de quase todos os idiomas, usa 1 bit de sinal, 11 bits de expoente e 52 bits de mantissa. Esta ferramenta pega qualquer número decimal que você digita, codifica-o em qualquer largura usando o próprio codificador flutuante DataView/ArrayBuffer do navegador - o mesmo mecanismo em que seu código realmente é executado - e colore cada bit de acordo com a região à qual ele pertence.
A codificação em si segue três etapas, e esta ferramenta percorre todas elas em vez de apenas mostrar os bits finais. Primeiro, normalização: o número é reescrito como 1.mantissa × 2 ^ expoente, com exatamente um dígito diferente de zero antes do ponto binário (números muito pequenos para isso recebem o tratamento subnormal, sem nenhum 1 inicial implícito). Segundo, polarização de expoente: como o campo de expoente deve armazenar expoentes positivos e negativos como um número sem sinal, uma polarização fixa (127 para precisão simples, 1023 para dupla) é adicionada antes de o campo ser escrito em binário. Terceiro, a mantissa fracionária é expandida bit a bit com o método clássico “multiplicar por dois, tomar a parte inteira como o próximo bit, manter o restante” – repetido até que o campo da mantissa esteja cheio, momento em que os bits restantes decidem se o resultado é arredondado para cima ou para baixo (arredondado da metade para par, o padrão IEEE-754).
É exatamente por isso que um número tão comum como 0,1 não é armazenado com exatidão. Em binário, 0,1 é uma fração repetida (0,0001100110011…, para sempre), então nenhuma mantissa finita pode mantê-la com precisão – ela precisa ser arredondada em algum lugar. Codifique 0,1 como um float de 32 bits e leia os bits de volta, e você não receberá 0,1 de volta: obterá exatamente 0,100000001490116119384765625. A precisão dupla tem mais 29 bits de mantissa para trabalhar, então seu erro de arredondamento é muito menor, mas também não é zero - 0,1, pois um duplo de 64 bits é exatamente 0,1000000000000000055511151231257827021181583404541015625. Esta ferramenta calcula esse valor armazenado exato com aritmética de números inteiros grandes (sem atalhos de ponto flutuante), de modo que a lacuna entre o que você digitou e o que o hardware realmente mantém nunca fica oculta ou aproximada.
Valores especiais obtêm padrões de bits exatos e reservados em vez de uma codificação normal: zero e zero negativo têm expoente totalmente zero e bits de mantissa (apenas o bit de sinal difere), ±Infinity possui todos os bits de expoente com uma mantissa totalmente zero e NaN possui todos os bits de expoente com pelo menos um bit de mantissa definido. Tudo aqui é executado localmente em seu navegador - nada que você digita é carregado em qualquer lugar - o que torna este um lugar seguro para construir uma intuição real para bugs de ponto flutuante: por que `0.1 + 0.2 !== 0.3` em quase todas as linguagens de programação, por que as texturas float32 no código gráfico perdem a precisão que float64 não perderia e por que o código financeiro geralmente deveria evitar completamente o ponto flutuante binário.