Como representar números utilizando bits
Como vimos até então, o mundo digital opera exclusivamente em base 2 (binário). Isso significa que temos apenas os dígitos 0 e 1 disponíveis em vez dos 10 dígitos disponíveis no sistema decimal.
A conversão entre números de base 2 e base 10 é relativamente simples. A imagem abaixo exemplifica essa operação.
![Conversão b2 - b10](https://www.onlinemath4all.com/images/decimaltobinary1.png)
Vamos treinar um pouquinho?
Converta o número a seguir de base 2 para base 10:
0010 1010
Converta o número a seguir de base 2 para base 10:
1001 1110
Nos exercícios anteriores eu tomei o cuidado de separar um número de 8 bits em duas partes iguais de 4 bits. Isso é uma prática comum principalmente no meio da computação. Por quê isso é conveniente? Qual a relação disso com a maneira como programamos computadores?
1. Números inteiros
A conversão acima já nos indica como é possível utilizar uma representação binária para valores inteiros, portanto não vou extender essa seção desnecessariamente. O que resta para nós? Bom, como podemos representar números negativos? Existem duas formas; utilizando sinal e magnitude ou utilizando complemento de dois.
1.1. Representação em sinal-magnitude
A representação por sinal-magnitude é talvez a mais intuitiva possível. Basicamente separamos o bit mais significativo do número inteiro para representar o sinal do número (se é negativo ou positivo). O restante do número é a magnitude dele. A figura abaixo demonstra essa forma de representação.
![sinal-magnitude](https://media.geeksforgeeks.org/wp-content/uploads/20200427140428/signed-1.png)
Considerando números inteiros com sinal em representação de sinal-magnitude, converta o número abaixo para a base decimal:
1010 1100
Quando utilizamos essa forma de representação, a disposição dos números possíveis com 4 algarismos fica como disposto na figura abaixo.
![Tabela sinal-magnitude 4 bits](https://miro.medium.com/v2/resize:fit:1200/1*oG66gGQ_-8CpHG7g7XWAXw.png)
Apesar dessa representação ser bastante intuitiva, há dois principais problemas com ela:
- Para operações de adição e subtração, precisamos primeiro verificar o bit de sinal sempre. Isso faz com que os circuitos combinacionais fiquem mais complexos.
- Há duas formas distintas de representar o número 0. Uma das operações mais comuns na computação é justamente verificar se um número (ou resultado de operação) é 0. Ter duas representações distintas para este valor é bastante inconveniente.
Não por acaso, a representação sinal-magnitude não costuma ser utilizada na computação. Prefere-se utilizar a representação por complemento de dois.
1.2. Representação em complemento de dois
A representação por complemento de dois é muito parecida com a de sinal-magnitude. Começamos pelo bit mais significativo, que também indica o sinal do número. Onde está a diferença, então? Bom, vamos olhar novamente a Fig. 7.03. Notou que tem uma coluna lá chamada 2's? Pois bem, aquela é a coluna que mostra o valor em decimal para a representação em complemento de 2.
Perceba que o range de valores muda um pouco. Antes, tínhamos uma representação possível de -7 a 7. Com complemento de dois, podemos representar números de -8 a 7. O motivo disso é que eliminamos a possibilidade de representar -0. Além disso, é possível notar que a distribuição do complemento de dois está simétrica. Na representação sinal-magnitude, passamos direto do 7 para -0. Enquanto isso, na representação por complemento de dois, passamos de 7 para -8. Essa simetria é uma consequência de uma característica dessa representação que é muito útil para operações aritméticas. A figura 7.04 representa a distribuição de valores de uma forma em que essa simetria fica mais evidente.
![Rosa dos ventos do complemento de dois.](https://electricalengineering123.com/wp-content/uploads/2019/12/Number-Circle-for-4-bit-Twos-Complement-Numbers.gif)
A característica ao qual me referia é uma particularidade na conversão entre números binários em complemento de dois para decimal. Vimos anteriormente que tipicamente a conversão binário-decimal envolve a soma de números dois com expoente referente à posição do algarismo (Figura 7.01). A figura 7.05 apresenta a versão atualizada da conversão, considerando a representação em complemento de dois.
![Comp 2 conversion](/arch/assets/images/comp2-d5db0f81b602244bf009896c2e3ae6e6.png)
Converta o número binário acima em decimal. Considere que o número está representado utilizando complemento de dois.
A vantagem de usar a representação em complemento de dois fica mais evidente quando consideramos a operação artimética de subtração e adição. O que ocorre é que essa representação permite que a subtração seja o mesmo que a soma com um número negativo, simplificando os circuitos combinacionais necessários para implementar as operações artiméticas.
Para que seja possível utilizar o complemento de dois na subtração, é importante ser capaz de inverter um número. Isso significa transformar um número positivo em sua contrapartida negativa e vice versa. O valor de 7 em binário, por exemplo, é 0111 e sua contrapartida negativa (-7) é 1001 em complemento de dois.
Existe uma sequência de operações relativamente simples para fazer essa conversão para qualquer número. Pesquise-a e teste-a com os seguintes números (use 8 bits):
- 24
- 123
- 0
2. Números reais
Boa parte dos valores que encontramos em problemas de cálculo numérico pertencem ao conjunto dos números reais e não dos inteiros. Sendo assim, é interessante discutir como podemos representar esses números em base 2. Para isso, vamos primeiro considerar com cuidado a maneira como representamos números fracionários em base 10. A figura 7.06 apresenta a maneira como trabalhamos com esse tipo de números.
![Float decimal](/arch/assets/images/float_dec-f93723770adecda6723253600e3b8b83.png)
A maneira como lidamos com esses números é basicamente tratando a parte fracionária como uma soma de todas as frações em base 10. Podemos fazer a mesma coisa em base 2. O resultado pode ser visto na figura 7.07.
![Float binary](/arch/assets/images/float_bin-bdf75621ca6edcd4fcc82cdf5895119c.png)
Converta o número real representado acima para a base decimal
O problema? Não é exatamente uma forma eficiente de armazenar esses valores. Considerando o exemplo acima, em que temos 6 bits para representar os valores reais, a situação é a seguinte:
- Valor máximo possível - 7.875
- Valor mínimo possível - 0.125
Beleza, mas não vamos usar só 6 bits para representar esse número, né? Vamos considerar que um float típicamente é representado com 32 bits (4 bytes). Se utilizarmos 16 bits para guardar a parte inteira e 16 para a parte fracionária, temos:
- Valor máximo possível - 1023.9998919189
- Valor mínimo possível - 0.00001226452
Parece...bom, né? Calma aí. Note que não conseguimos chegar a números muito altos (~1024 é o máximo) e simultaneamente não chegamos nem em números tão pequenos assim. E isso sem nem considerar números negativos. Para colocar em perspectiva, se o valor mínimo possível fosse em metros, isso significaria que teríamos chegado apenas ao centésimo de milimetros. Isso não é o limite do aceitável para trabalhar com usinagem mecânica de precisão, quanto mais qualquer outra operação que necessite de uma precisão realmente alta.
2.1. Números de ponto flutuante
Vamos dar uma olhadinha nos valores reais do float?
- Valor máximo possível - 3.402823466 x 10^38
- Valor mínimo possível - 1.175494351 x 10^-38
Pera, o quê? Esse range é muito maior do que o que conseguimos na nossa representação!
Pois é. A maneira como o float é representado não tem nada a ver com aquela divisão de parte inteira e parte fracionária que vimos ali em cima. Na verdade, vou até roubar a definição da wikipedia em inglês para representação de pontos flutuantes. Acho que vai dar uma dica do que está acontecendo:
Na computação, aritmética de ponto flutuante é a aritmética que representa um subconjunto de números pertencentes ao conjunto dos números reais utilizando um valor inteiro de precisão fixa, chamado de significando, escalado por um inteiro expoente com uma base fixa. Números com esse formato são chamados de números de ponto flutuante. Por exemplo, o número 12.345 em formato de ponto flutuante com cinco dígitos de precisão e base 10 fica:
Pois é. Números de ponto flutuante usam uma notação muito parecida com a notação científica e isso faz toda a diferença. Não, não é mágica. E sim, eu estou ciente de que não tem como representar uma quantidade maior de números com o mesmo número de bits. A genialidade dessa abordagem está na distribuição desses números. Veja a imagem 7.09:
![Ponto flutuante](https://upload.wikimedia.org/wikipedia/commons/thumb/b/b6/FloatingPointPrecisionAugmented.png/1920px-FloatingPointPrecisionAugmented.png)
Pois é... A resposta é simples quando vemos assim: basta usar mais desses números disponíveis quando a granularidade é mais importante. Isto é, quando o valor numérico está perto de zero. Por outro lado, quando o valor se aproxima do limite superior ou do limite inferior da notação, há cada vez menos granularidade nesses valores. Faz sentido, pois importa muito mais 0.00001 quando o valor total é 0.00021 do que quando é 2000000000.00001. O único ponto de atenção é que a mudança de float para double não é mais tão simples de decidir. Temos que olhar para a precisão e não mais para o range de valores possíveis.
Para encerrar essa incursão nos números reais representados em computador, vamos falar sobre um padrão. Afinal, eu posso escolher uma base diferente. De forma similar, posso escolher também tamanhos diferentes (em bits) para a base, expoente e significando. Entra o IEE 754.
2.3. Padrão IEEE 754
O vídeo abaixo mostra como funciona o padrão IEEE 754 para a representação de números de ponto flutuante na computação.
Vamos finalizar com alguns exercícios?
Expresse os seguintes números em formato IEE de ponto flutuante com 32 bits:
- -5
- -6
- -1,5
- 384
- 1/16
- -1/32
Considere um formato hipotético IEE de 7 bits, com 3 bits para o expoente e 3 bits para a mantissa. Quantos valores é possível representar? Qual o valor máximo? Qual o valor mínimo? Quais os dois valores mais próximos de 0 possíveis?