Olá, Khabrovites. Eu gosto muito do tópico de registros de ponto flutuante há muito tempo. Eu sempre estava preocupado com a forma como a saída para a tela, etc. Lembro-me, há muito tempo, na universidade, eu estava implementando minha classe de números de ponto flutuante que consistia em 512 bits. A única coisa que eu não conseguia perceber era a saída para a tela.
Assim que tive tempo livre, peguei o velho. Peguei um caderno e vamos embora. Eu queria pensar em tudo sozinho, apenas ocasionalmente olhando para o padrão IEEE 754.
E é isso que veio de tudo. Para os interessados, peço gato.
Para dominar este artigo, você precisa saber o seguinte: o que é um pouco, um sistema binário, aritmética no nível de conhecimento de graus negativos. O artigo não afetará os detalhes de engenharia da implementação no nível do processador, bem como os números normalizados e desnormalizados. Mais ênfase é colocada na conversão de um número em formato binário e vice-versa, além de explicar como os números de ponto flutuante são geralmente armazenados na forma de bits.
Os números de ponto flutuante são uma ferramenta muito poderosa que você precisa para poder usar corretamente. Eles não são tão comuns quanto os registros inteiros, mas também não são tão complexos se forem penetrados de forma competente e lenta.
No artigo de hoje, usarei registradores de 32 bits como exemplo. Os números de precisão dupla (64 bits) funcionam exatamente da mesma lógica.
Primeiro, vamos falar sobre como os números de ponto flutuante são armazenados. Os 31 bits mais antigos são significativos. Um único significa que o número é negativo e zero, respectivamente, é o oposto. Em seguida, vêm 8 bits do expoente. Esses 8 bits são o número não assinado usual. E no final são 23 bits da mantissa. Por conveniência, denotamos o sinal como S, o expoente como E, e a mantissa, curiosamente, M.
Temos a fórmula geral
A mantissa é considerada um único bit implícito. Ou seja, a mantissa terá 24 bits, mas como o 23º bit mais alto é sempre um, você não pode anotá-lo. Assim, essa "restrição" nos dará a singularidade de representar qualquer número.
Mantissa é um número binário comum, mas, diferentemente dos números inteiros, o bit mais significativo é 2 ^ 0 graus e depois em graus decrescentes. É aqui que o expositor é útil. Dependendo do seu valor, a potência do bit alto dois aumenta ou diminui. Essa é a genialidade dessa idéia.
Vamos tentar mostrar isso com um bom exemplo:
Imagine o número 3.625 em formato binário. Primeiro, dividimos esse número em potências de dois.
O grau dos dois mais velhos é igual a um. E - 127 = 1. E = 128.
0 1.000.000 1.101.000.000.000.000.000.000
Esse é todo o nosso número.
Vamos tentar também na direção oposta. Suponha que temos 32 bits, 32 bits arbitrários.
0 10000100 (1) 11011100101000000000000
O mesmo bit implícito de ordem superior é indicado entre colchetes.
Primeiro, calcule o expoente. E = 132. Consequentemente, o grau dos dois idosos será igual a 5. No total, temos o seguinte número:
É fácil adivinhar que só podemos armazenar um intervalo de 24 graus dois. Assim, se dois números diferem exponencialmente em mais de 24, então, quando adicionados, o número permanece igual ao maior entre eles.
Para uma conversão conveniente, enviei um pequeno programa em C.
#include <stdio.h> union IntFloat { unsigned int integerValue; float floatValue; }; void printBits(unsigned int x) { int i; for (i = 31; i >= 0; i--) { if ((x & ((unsigned int)1 << i)) != 0) { printf("1"); } else { printf("0"); } if (i == 31) { printf(" "); } if (i == 23) { printf(" "); } } printf("\n"); } int main() { union IntFloat b0; b0.floatValue = 59.578125; printBits(b0.integerValue); b0.integerValue = 0b01000010011011100101000000000000; printf("%f\n", b0.floatValue); return 0; }
A etapa da grade é a diferença mínima entre dois números de ponto flutuante adjacentes. Se representarmos uma sequência de bits desse número como um número inteiro regular, o número de ponto flutuante vizinho será diferente em bits como um número inteiro por unidade.
Pode ser expresso de outra forma. Dois números de ponto flutuante adjacentes diferirão em 2 ^ (E - 127 - 23). Ou seja, por uma diferença igual ao valor do bit menos significativo.
Como prova, você pode alterar main no código e compilar novamente.
union IntFloat b0, b1, b2; b0.floatValue = 59.578125F; b1.integerValue = b0.integerValue + 1; b2.floatValue = b1.floatValue - b0.floatValue; printBits(b0.integerValue); printBits(b1.integerValue); printBits(b2.integerValue); printf("%f\n", b0.floatValue); printf("%f\n", b1.floatValue); printf("%f\n", b2.floatValue); short exp1 = 0b10000100; short exp2 =0b01101101; b0.integerValue = 0b01000010011111111111111111111111; b1.integerValue = b0.integerValue + 1; b2.floatValue = b1.floatValue - b0.floatValue; printBits(b0.integerValue); printBits(b1.integerValue); printBits(b2.integerValue); printf("%f\n", b0.floatValue); printf("%f\n", b1.floatValue); printf("%f\n", b2.floatValue); printf("%d %d\n", exp1, exp2);
Eu acho que por hoje você pode terminar, caso contrário acaba muito tempo. Na próxima vez, escreverei sobre como adicionar números de ponto flutuante e perder precisão ao arredondar.
PS: Entendo que não toquei no tópico de números desnormalizados, etc. Eu simplesmente não queria carregar muito o artigo, e essas informações podem ser facilmente encontradas no padrão IEEE 754 quase no início.