# Introdução à Arquitetura de Computadores/Representação Numérica

Números podem ser representados em qualquer tipo de base. Humanos costumam representar números na base decimal. Esta possui este nome por possuir 10 dígitos diferentes: 0, 1, 2, 3, 4, 5, 6, 7, 8 e 9.
Como computadores só são capazes de reconhecer dois tipos diferentes de estados, é natural para eles representar números na base binária. Ou seja, eles só compreendem os valores "0" e "1". Vamos ver agora coo podemos representar diversos tipos de números usando apenas dois tipos diferentes de sinais.

## Números Naturais

Representar Números Naturais é simples bastante simples - mesmo quando só podemos usar dois tipos diferentes de sinais. O 0 decimal é representado como 0. O 1 decimal também é representado como 1. Já o 2 decimal é representado com "10". Enfim, quando contamos os números naturais na base binária, fazemos da seguinte forma:
Quando representamos números desta forma, não temos números negativos. Logo, nenhum número possui sinal algum. Por isso, chamamos tais números de unsigned (sem sinal).
Atualmente, a maioria das máquinas possui representação numérica 32 bits. Isso significa que seus números são compostos por 32 dígitos. Com uma representação destas, o menor número binário sem sinal que podemos representar é 00000000000000000000000000000000 e o maior é 1111111111111111111111111111111. Convertemos tais valores para decimal, chegamos à conclusão que a maioria dos computadores só lida com números sem sinal cujo valor esteja entre 0 e 4.294.967.295 - que são os equivalentes decimais destes números.
Para descobrir qual é o equivalente decimal de um número binário de 32 bits, usa-se a fórmula abaixo:
$N_{0}*2^{32}+N_{1}*2^{31}+N_{2}*2^{30}+N_{3}*2^{29}+(...)+N_{29}*2^{3}+N_{30}*2^{2}+N_{31}*2^{1}+N_{32}*2^{0}$
onde $N_{0}$ é o dígito mais significativo e $N_{32}$ é o bit menos significativo.

## Números Inteiros

Nem sempre os números com os quais queremos lidar são naturais. Pode haver necessidade de realizarmos operações com números negativos. Para tal, devemos encontrar uma forma de representarmos números negativos. Uma das primeiras formas tentadas de fazer isso foi reservando o primeiro bit de um número para representar o sinal. Assim, um 0000000 (...) 001 representa +1 e um 10000 (...) 001 representaria um -1. Entretanto, tal forma de representação já foi abandonada há muito tempo. Um dos principai motivos para o abandono desta representação está no fato de sempre termos que verificar o primeiro bit para descobrir como efetuar uma soma ou subtração entre dois números. Além disso, tal representação tornaria possível um "+0" e um "-0".
A forma de se representar números inteiros em computadores modernos é chamada de Representação em Complemento de 2. Ela é feita da seguinte forma:
Os 31 bits mais à esquerda representam sempre números positivos. Calculamos o que eles representam com a mesma fórmula vista acima. Entretanto, o primeiro bit representa sempre "0" se o seu valor for "0" ou "$-2^{32}$ se o seu valor for "1". Assim, a fórmula para se calcular o que representa um número binário inteiro em decimal é:
$-N_{0}*2^{32}+N_{1}*2^{31}+N_{2}*2^{30}+N_{3}*2^{29}+(...)+N_{29}*2^{3}+N_{30}*2^{2}+N_{31}*2^{1}+N_{32}*2^{0}$
onde $N_{0}$ é o dígito mais significativo e $N_{32}$ é o bit menos significativo.
A representação em complemento de 2 é boa pelos seguintes motivos:
* Para somar dois números inteiros, usa-se o mesmo método. Não precisamos verificar o sinal deles. Com isso, podemos criar um circuito mais rápido.
* Descobrir o inverso de um número também é simples. Basta invertermos todos os bits e, em seguida, somarmos 1. Sempre funciona.
* O "0" é sempre "0". Não existe uma versão negativa ou positiva deste número.
Por meio desta notação, números tão pequenos como -4.294.967.296 e tão grandes como 4.294.967.295 podem ser representados.

## Números Reais

Por fim, também é muito útil que computadores possam representar números reais. Vamos estudar agora como eles costumam ser representados em computadores.
A forma mais lógica e versátil de representarmos um número real é por meio da notação científica. Um número decimal em notação científica sempre toma a seguinte forma:
$N*10^{M}$
onde N é sempre um número real maior ou igual à 1 e menor que 10 que pode ser positivo ou negativo enquanto M é um número inteiro qualquer (que pode ser positivo ou negativo). Por exemplo:
$3,15756*10^{9}$ representa aproximadamente quantos segundos existe em um século enquanto
$1,00000*10^{-9}$ representa quantos segundos existem em um nanossegundo.
Alternativamente, podemos representar também números em notação científica utilizando uma base binária:
$1,00000*10^{-1}$. Lembre-se que aquele "10" significa na verdadee "2" em representação decimal. Ou seja, o número representado é na verdade 0,5 e não 0,1.
Enfim, quando temos um número em notação científica binária, ele sempre tem a seguinte forma:
$S*1,XXXXX*10^{YYYYY}$ , onde "S" representa o sinal (que pode ser positivo ou negativo), "XXXXX" são a parte fracionária do número e "YYYYYY" representa o expoente.
Então, para representarmos números reais, precisamos representar no espaço de 32 bits os valores do sinal, fração e expoente. No MIPS, isso é feito da seguinte forma:
O Sinal é representado em um único bit (1 para negativo e 0 para positivo), o expoente é representado por 8 bits e a fração é representada por 23 bits. Assim, o exemplo $1,00000*10^{-1}$ (ou 1/2 em decimal) é representado:
Com esta representação, números quase tão pequenos como $-2^{38}$ ou quase tão grandes como $2^{38}$ podem ser armazenados.
Entretanto, note que a nossa precisão não é perfeita. Existem muitos números que não podem ser representados desta forma. Por isso, sempre é preciso realizar arredondamentos. E quanto mais operações realizamos, mais erros acabam sendo acumulados. Por essa razão, a maioria dos computadores possui também uma forma mais precisa de representação de números reais. Essa forma normalmente ocupa o dobro do espaço do que costuma ocupar. No MIPS uma representação desti tipo ocupa 64 bits. Ela funciona da seguinte forma:
Apesar de gastarmos o dobro do espaço de armazenamento desta forma, aumentamos consideravelmente a faixa de valores representados e a precisão com a qual podemos representá-los.

## Overflow e Underflow

Overflow é o nome do resultado incorreto ao qual chegamos quando tentamos somar números grandes demais e o resultado não pode ser armazenado e nem representado na representação usada, que por exemplo poderia ser 32 bits. Ele pode ocorrer quando efetuamos as seguintes operações:
* Somamos dois números positivos
* Somamos dois números negativos
* Subtraímos um número negativo de um positivo.
É de responsabilidade do hardware sempre verificar se ocorreu um overflow nestes casos. Normalmente, para fazer a verificação basta conferir os sinais dos números. Em caso afirmativo, ele deve ativar um sinal de exceção. O que é feito caso uma exceção seja detectada depende de cada programa.
O MIPS, por exemplo, sempre armazena o endereço da última instrução que gerou exceção em um registrador especial chamado EPC. A instrução mfc0 (Move from System Control) é usada para copiar este endereço para um registrador de propósito geral que pode ser conferido por algum programa. Cabe ao programador desse programa decidir o que fazer.
Em linguagens de alto nível, o comportamento depende da linguagem usada. C, por exemplo, ignora todos os Overflows. Ao contrário de Fortran ou Ada que requerem que o programa sempre seja avisado.
Underflow é o que acontece quando lidamos com um número real tão próximo de 0 que o valor do expoente não pode ser corretamente representado. Quando um programa requer uma precisão tão grande assim, recomenda-se o uso de pontos flutuantes de dupla precisão (double) para evitar este inconveniente.