Projeto de Algoritmos

Representação de números inteiros

A memória de qualquer computador é uma seqüência de bytes.  Cada byte tem um endereço, que dá sua posição na memória. Cada byte consiste em 8 bits (= dígitos binários). Portanto, um byte tem 256 possíveis valores:

000000000000000100000010,  . . . ,  1111111011111111.

Como é possível representar um número natural em um byte ou em alguns bytes consecutivos? Como é possível representar um número inteiro, positivo ou negativo, em um byte ou em alguns bytes consecutivos? Como essas representações podem ser manipuladas para realizar operações aritméticas sobre os números?

Representação binária de números naturais

Na linguagem C, números naturais — 0, 1, 2, 3, etc. — são conhecidos como "inteiros sem sinal".  Esse é um dos tipos-de-dados básicos da linguagem C.  Um variável n desse tipo é declarada assim:

   unsigned int n;

Em alguns computadores, um unsigned int é armazenado em 2 bytes consecutivos. Em muitos outros, é armazenado em 4 bytes consecutivos. Ou seja, sizeof (unsigned int) vale 4 em muitos computadores.  [O operador sizeof dá o número de bytes de um objeto.]

Para simplificar nossos exemplos, vamos imaginar que cada byte tem apenas 2 bits e não os 8 bits usuais. Vamos imaginar também que cada inteiro é armazenado em 2 bytes. Portanto, cada inteiro em nosso computador imaginário terá um total de 4 bits. Uma variável inteira sem sinal — tal como n no exemplo acima — poderá assumir 24 = 16 valores diferentes:

0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 .

[Nos computadores reais temos 216 = 65 536 ou até 232 = 4 294 967 296 valores diferentes.]  A correspondência entre os padrões de 4 bits e os números naturais é dada pela tabela abaixo:
valor bits 
0 0000
1 0001
2 0010
3 0011
4 0100
5 0101
6 0110
7 0111
8 1000
9 1001
10 1010
11 1011
12 1100
13 1101
14 1110
15 1111

É fácil entender a tabela: 13 corresponde à seqüência de bits 1101 porque

13 = 23 + 22 + 21 + 20.

Você deve imaginar que a tabela é cíclica: o fim é emendado com o começo, ou seja, depois do padrão 1111 vem, novamente, o padrão 0000. As operações aritméticas seguem o ciclo. [Um matemático diria que todas as operações aritméticas são executadas módulo 16.] Por exemplo,

9+6  vale  15,    9+7  vale  0,    9+8  vale  1    e    14×2  vale  12.

Nos três últimos exemplos ocorre overflow (= transbordo). Isso não constitui um erro e o computador continua trabalhando normalmente.

Exercícios

  1. Suponha que nosso computador representa cada inteiro em 2 bytes e que cada byte tem 8 bits.  Qual o efeito do seguinte fragmento de código?
       unsigned int i;
       for (i = 0; i < 65536; i++) x[i] = 0;
    

  2. Projeto de Programação.  Escreva um programa que receba um número inteiro positivo n e imprima as potências  n2, n3, n4, n5 etc.  O programa só deve parar quando não for capaz de armazenar uma potência em um unsigned int.

Representação "two's-complement" de inteiros

Há dois sabores de números inteiros: o positivo e o negativo. Os inteiros positivos podem ser representados em notação binária exatamente como os números naturais. Como é possível representar os inteiros negativos? Queremos uma representação que permita executar operações aritméticas de forma simples e mecânica.

Na linguagem C, os inteiros são conhecidos como "inteiros com sinal". Uma variável inteira i é declarada assim:

   int i;

Para simplificar os exemplos, vamos imaginar, mais uma vez, que em nosso computador cada byte tem apenas 2 bits e não os 8 bits usuais. Portanto, cada inteiro em nosso computador imaginário terá um total de 4 bits. Uma variável inteira tal como i poderá assumir 24 = 16 valores diferentes.  A convenção usual manda usar esses 16 valores para representar os seguintes números:

–8, –7, –6, –5, –4, –3, –2, –1, +0, +1, +2, +3, +4, +5, +6, +7 .

(Poderíamos representar o intervalo que vai de –7 a +8, ou o intervalo que vai de –6 a +9, ou algo ainda mais bizarro; mas isso não se faz normalmente.) Para associar esse intervalo com os padrões de 4 bits, usa-se a tabela abaixo, que segue a convenção conhecida como two's-complement:
valor bits 
–8 1000
–7 1001
–6 1010
–5 1011
–4 1100
–3 1101
–2 1110
–1 1111
+0 0000
+1 0001
+2 0010
+3 0011
+4 0100
+5 0101
+6 0110
+7 0111

Os padrões de bits que começam com 1 representam números negativos. Para descobrir o padrão de bits de um inteiro negativo faça o seguinte: tome o padrão de bits do correspondente número positivo, inverta todos os bits (ou seja, troque 0 por 1 e 1 por 0) e some 1 (em binário) ao resultado.
            0
       -1      +1
    -2            +2
  -3                +3
                      
 -4                  +4
                      
  -5                +5
    -6            +6
       -7      +7
           -8

Você deve imaginar que a tabela é cíclica: o fim é emendado com o começo, ou seja, depois do padrão 0111 vem o padrão 1000. As operações aritméticas seguem esse ciclo. Para somar 7 com 3, por exemplo, localize 7 na tabela e dê 3 passos à frente, sem esquecer que a tabela é cíclica. Assim,

7+1  vale  –8  (e não +8, como você gostaria),
7+3  vale  –6  (e não +10, como você esperava),
6×2  vale  –4  (e não +12, como querem os matemáticos).

Nas três últimas somas ocorre overflow (= transbordo). Isso não constitui um erro e o computador continua trabalhando normalmente.

Nos computadores reais, os valores de int vão usualmente de –231 a 231–1, ou seja, de –2 147 483 648 a 2 147 483 647.  Os valores mínimo e máximo no seu computador estão registrados nas constantes INT_MIN e INT_MAX definidas no arquivo limits.h.

A possibilidade de overflow sugere que em trechos de código como

while (...) {
   ...
   i = i + 1;
}

o programador deveria interromper o programa se i for igual a INT_MAX antes do incremento de i. Isso não se faz, entretanto, pois em geral outras partes do código garantem indiretamente que i ≤ INT_MAX.

Exercícios

  1. Suponha, como fizemos acima, que cada número inteiro é representado em nosso computador por 4 bits. Imagine agora que (ao contrário da convenção two's-complement) cada inteiro negativo entre –7 e –1 é representado da seguinte maneira: primeiro, o valor absoluto do número é representado em binário, da maneira usual; depois, o bit mais significativo (ou seja, o que está mais à esquerda) é ligado (ou seja, assume valor 1).  Por exemplo, –5 é representado por  1101 .  Discuta as desvantagens desse sistema de representação de números negativos.

  2. Suponha que precisamos contar o número de ocorrências de um certo fenômeno.  Sabemos de antemão que o fenômeno não ocorre mais que 65535 vezes.  Suponha que nosso computador representa cada inteiro em 2 bytes e que cada byte tem 8 bits.  Podemos usar uma variável do tipo unsigned int para contar as ocorrências do fenômeno?  Que fazer se o fenômeno pode ocorrer mais vezes? Proponha uma solução que use listas encadeadas para representar um contador em base 100 (ou base 256, ou base 65536). 

Representação de inteiros por cadeias de caracteres

Há uma outra representação possível para números inteiros: um inteiro como –123, por exemplo, é representado pela seqüência de caracteres

'-'    '1'    '2'    '3'

Na linguagem C, esta cadeia de caracteres (= string) é representada por  "-123".  Se estivermos usando a tabela ISO 8859-1, a seqüência de caracteres será

45    49    50    51

Como cada caracter é armazenado em um byte e cada byte tem 8 bits, o resultado é a seqüência de quatro bytes

00101101 00110001 00110010 00110011

Para estabelecer o contraste entre essa representação e as anteriores, observe que a representação two's-complement de –123 é

11111111 10000101

(confira!) supondo que cada int é armazenado em 2 bytes. A representação de inteiros por strings não é muito econômica (consome muitos bytes). Além disso, fazer operações aritméticas diretamente sobre essa representação é uma loucura!  Por tudo isso, a representação não é usada na memória do computador.

Entretanto, a representação de inteiros por strings é muito usado em arquivos.  Se um arquivo usa essa maneira de representar inteiros, dizemos que ele está em "modo texto" (text file);  se usa a representação binária ou two's-complement, dizemos que está em "modo binário" (binary file).

A função  atoi  (abreviatura de alphanumeric to integer) converte representação por string em representação "two's-complement".  Por exemplo,

atoi ("9876")   vale   9876

e   atoi ("-9876")   vale   -9876 .  [Pergunta irrelevante: quanto vale atoi ("C3PO")?]   A função atoi faz parte da biblioteca stdlib, e portanto seu protótipo está no arquivo-interface stdlib.h.

 

 


URL of this site: www.ime.usp.br/~pf/algoritmos/
1998 | Last modified: Thu Apr 7 08:06:40 BRT 2005
Paulo Feofiloff
IME-USP