Até então só estivemos lidando com números inteiros, porém não será muito difícil extender nosso conhecimento às frações, uma vez que usaremos os mesmos mecanismos que já estamos familiarizados. Frações binárias introduzem alguns comportamentos interessantes como iremos ver.
Como já sabemos, binário é um sistema numérico posicional, também é um sistema numérico de base. Ao movermos uma posição (ou dígito) para a esquerda a potência que nós multiplicamos a base (2 em binário) aumenta em 1, ao movermos para a direita nós diminuimos a potência em 1.
Por exemplo, o número decimal 34.482 é traduzido em:
3 * 10^1 = 30
4 * 10^0 = 4
4 * 10^-1 = 4/10
8 * 10^-2 = 8/100
2 * 10^-3 = 2/1000
Da mesma forma funciona em binário, porém usamos a potência 2, considere o número 101.101 que se traduz em:
1 * 2^2 = 4
0 * 2^1 = 0
1 * 2^0 = 1
1 * 2^-1 = 1/2 = 0.5
0 * 2^-2 = 0
1 * 2^-3 = 1/8 = 0.125
Calculando: 4 + 1 + 0.5 + 0.125 = 5.625
Em decimais existem diversas frações que não conseguimos representar com acurácia, 1/3 seria uma delas, podemos até chegar próximos (0.3333333333333), porém nunca chegaremos a representação exata, isso é o mesmo para frações binárias, entretanto o número de valores em binário que não podemos representar é ainda maior.
Até então olhamos para o que é chamado de frações binárias de ponto fixo, essas são maneiras convenientes de representar números, porém logo que o número que quisermos representar é muito maior ou muito menor nós notamos que precisaremos um número muito grande de bits para representá-los. Se nós quisermos por exemplo representar o valor decimal 128 será requerido 8 dígitos binários (10000000), isso é mais que o dobro de números de dígitos para representar o mesmo valor, e isso só piora ao nos distanciarmos do zero.
Para dar a volta nessa situação, utiliza-se o método de representação de números chamado ponto flutuante. Ponto Flutuante é similar à notação científica como meio de representação de números, perdemos um pouco de acurácia, entretando, ao lidarmos com números muito grandes ou muito pequenos é geralmente aceitável. A seguir falaremos do padrão IEEE para números de ponto flutuantes, uma vez que é o padrão que todos usam.
Relembrando a notaçao científica, vejamos como ela funciona:
1230000 = 1.23 x 10^6 -> Expoente
Mantissa
No exemplo acima, 1.23 é o que chamamos de mantissa e 6 é o que chamamos de expoente. A mantissa é sempre ajustada de uma maneira que apenas um único dígito (não zero) fique a esquerda do ponto decimal. O expoente nos mostra quantas casas mover o ponto, nesse caso movemos 6 casas para a direita, caso o expoente seja negativo, então movemos para a esquerda.
Então caso queiramos representar 1230000 em notação científica, fazemos o seguinte:
- Ajustamos o número de uma forma que apenas um único dígito fique a esquerda do ponto decimal, 1.23
- Para criarmos esse novo número nós movemos o ponto decimal 6 casas, ele se torna o expoente
- Sendo assim a notação científica fica 1.23 x 10^6
Em Binário nós procedemos da mesma maneira e isso forma a fundação do nosso número de ponto flutuante. Dessa vez não é um ponto decimal que estamos movendo, mas sim um ponto binário e pelo fato de se mover, chamamos de flutuante. O que veremos agora é chamado de padrão IEEE 754 para representar números de ponto flutuante. O padrão específica o número de bits usado para cada seção (expoente, mantissa e sinal) e a ordem em que eles são representados.
O padrão específica o seguinte formato para números de ponto flutuante:
Precisão única, que usa 32 bits e tem o seguinte layout:
1 bit para o sinal do número, 0 sendo positivo e 1 negativo 8 bits para o expoente 23 bits para a mantissa
Precisão dupla, que usa 64 bits e tem o seguinte layout:
1 bit para o sinal do número, 0 sendo positivo e 1 negativo 11 bits para o expoente 52 bits para a mantissa
Veja que a precisão dupla tem mais bits, possibilitando números muito maiores e muito menores a serem representados, conforme a mantissa é maior, o grau de acurácia também aumenta, porém vale lembrar que embora a precisão dupla tenha essas vantagens, ela também requer mais poder de processamento do nosso computador. Agora vamos olhar como números de precisão única funcionam (pelo fato de serem mais fácil de exemplificar), mas não se preocupe, os números de precisão dupla operam da mesma maneira, apenas possuem mais bits.
Esse é o primeiro bit (o bit mais a esquerda) nos números de ponto flutuante e é muito simples, como mencionado anteriormente, se o número for positivo, faça desse bit como 0, caso ele seja negativo, faça-o como 1.
O expoente torna o assunto mais interessante, lembre que o expoente pode ser positivo (para representar números maiores) ou negativo (para representar números menores, frações). Sua primeira impressão deve ser que o Complemento Dois seja o ideal para essa situação, porém o padrão tem uma solução diferente, ele é feito de uma forma que permita o processamento mais fácil de números de ponto flutuante.
Com 8 bits e um binário sem sinal nós podemos representar números de 0 a 255. Para possibilitar números negativos em um ponto flutuante nós pegamos o nosso expoente e adicionamos 127 a ele. O alcance de expoentes que podemos representar se torna 128 a -127. 128, porém não é aceito e é mantido como um caso especial para representar certos números especiais, veja abaixo
Caso queíramos que o expoente seja 5. 5 + 127 = 132, sendo assim nosso expoente se torna 10000100 Caso queíramos que o expoente seja -7. -7 + 127 = 120, sendo assim nosso expoente se torna 01111000
Um grande benefício desse método é que se o bit mais a esquerda for 1, então nós sabemos que ele é um expoente positivo e é um número grande sendo representado, e caso seja 0, então nós sabemos que o expoente é negativo e é uma fração (ou número pequeno).
Na notação científica, lembre que nós movemos o ponto de forma que tenha apenas um único (não-zero) dígito a esquerda dele. Quando fazemos isso com um dígito binário, o dígito deve ser 1, já que não há outra possibilidade. Os criadores do padrão ponto flutuante usaram isso para sua própria vantagem de modo a conseguirem mais dados representados em um número.
Depois de converter um número binário para notação científica, antes de guardar na mantissa, nós abandonamos o líder 1, isso nos possibilita guardar mais 1 bit de dado na mantissa.
Por exemplo, se nosso número a guardar for 111.00101101 então em notação científica será 1.1100101101 com o expoente 2 (nós movemos o ponto binário duas casas a esquerda), abandonamos o líder 1 e guardamos apenas 1100101101.
Se o nosso número a guardar for 0.0001011011 então em notação científica será 1.011011 com o expoente de -4 (nós movemos o ponto binário quatro casas a direita), abandonamos o líder 1 e guardamos apenas 011011.
Existem alguns casos especiais que devemos considerar:
Zero é representado fazendo o bit do sinal sendo 1 ou 0 e todos os outros bits 0
1 00000000 00000000000000000000000 ou 0 00000000 00000000000000000000000
Isso seria igual a uma mantissa de 1 com o expoente de -127, que seria o menor número que poderiamos representar em um ponto flutuante, não é 0, mas é muito próximo e os sistemas sabem interpretá-lo como zero.
É possível representar tanto positivo, como negativo infinito, é só uma questão de alterar o bit do sinal.
Para representar o infinito nós temos um expoente de todos os bits 1's com uma mantissa de todos 0's.
0 11111111 00000000000000000000000 ou 1 11111111 00000000000000000000000
Não-números são usados para representar algo que ocorreu que resultou em um número que não pode ser computado. Uma divisão por zero ou raiz quadrada de um número negativo por exemplo. Eles são representados por um expoente que tem todos os 1's e uma mantissa que é a combinação de 1's e 0's (porém não todos os 0's, uma vez que isso representaria infinito), o bit do sinal pode ser 1 ou 0.
0 11111111 00001000000000100001000 ou 1 11111111 11000000000000000000000
O padrão de 1's e 0's é usualmente usado para indicar a natureza do erro, entretanto isso é decidido pelo programador, uma vez que não há uma lista oficial de códigos de erros.
Valor | Representação |
---|---|
Zero | 0 00000000 00000000000000000000000 |
Zero Negativo | 1 00000000 00000000000000000000000 |
Infinito | 0 11111111 00000000000000000000000 |
Negativo Infinito | 1 11111111 00000000000000000000000 |
Não-número (NaN) | 0 11111111 00001000000000100001000 |