Escolar Documentos
Profissional Documentos
Cultura Documentos
Programação em C
Programação em C
k=0
a
k
b
k
Para que a representao de umnmero seja nica, necessrio que os valores de todos os
seus algarismos sejammenores do que b, ou seja, no mximo b1. Caso contrrio, o nmero
b
2
, por exemplo, poderia ser representado pelas duas sequncias (b, 0) e (1, 0, 0). Os valores
dos dgitos tambm no podem ser negativos seno surgiria mais uma ambiguidade na
representao.
importante nesse ponto no confundir os valores dos dgitos com as suas representa-
es (que costumam ser os algarismos arbicos sempre que possvel). Aqui, os a
j
simbo-
lizam os valores dos dgitos (como objetos matemticos abstratos), independentemente de
sua representao. Quando a base no ultrapassa 10, os valores confundem-se com suas re-
presentaes (usamos os algarismos de 0 a b 1 para representar os valores de 0 a b 1).
Quando a base maior que 10, precisamos de outra conveno para a base hexadecimal,
por exemplo, os dgitos com valores de 10 a 15 so representados pelas seis primeiras letras
do alfabeto, enquanto os dgitos at 9 continuam sendo representados da maneira usual.
A frmula acima nos permite, dados os valores dos dgitos de um nmero, achar o valor
do nmero. Podemos tambm querer realizar o processo inverso: dado um nmero, achar
O armazenamento dos dados 47
os valores dos seus dgitos na representao em base b. Olhando para a frmula acima,
podemos ver que, ao dividir (com resto) o valor do nmero por b, obtemos como resto o
valor de a
0
; o quociente o novo nmero
a
n
b
n1
+ a
n1
b
n2
+ + a
1
E, ao dividi-lo por b, obteremos como resto o prximo dgito, a
1
(que pode muito bem ser
zero). No difcil concluir que, se realizarmos n + 1 divises por b, os restos sero, na
ordem, os dgitos
(a
0
, a
1
, a
2
, . . . , a
n1
, a
n
).
Ou seja, dividindo repetidamente por b, obteremos os dgitos da direita para a esquerda.
Note que, na ltima diviso, obtemos a
n
como resto e zero como quociente.
Para ilustrar esse algoritmo, vamos fazer uma funo que recebe um nmero inteiro e
imprime seus dgitos na representao binria.
void imprime_binario(int numero)
{
while (numero > 0) {
printf("%d", numero % 2);
numero /= 2;
}
printf("\n");
}
Notemos, primeiro, que essa funo fornece os dgitos na ordem inversa (conforme foi
observado acima); por exemplo, o nmero 12 = (1100)
2
seria impresso como 0011.
Veja tambm que, em vez de xar o nmero de divises, estabelecemos como critrio
de parada o anulamento do quociente. Com isso, no precisamos saber de antemo quantos
dgitos binrios tem o nmero; vimos que o quociente ser zero quando s sobrar o dgito
mais signicativo.
4.2 Oarmazenamento dos dados
Ao armazenar um nmero na memria do computador, precisamos estabelecer um tamanho
padronizado (uma espcie de nmero de algarismos) que um nmero ocupar. Por que isso?
Imagine que a memria do computador uma enorme aglomerao de rodinhas numeradas.
Se no tivesse um tamanho padro, como saberamos onde comea e onde termina cada
nmero? Precisaramos de algum outro nmero para saber onde cada nmero comearia, e
essa histria no terminaria nunca.
Num computador, a menor unidade possvel de informao o bit; mas a menor unidade
de informao que pode ser de fato acessada o byte, que, nos microcomputadores moder-
nos, equivale a 8 bits. No entanto, um byte sozinho no serve para muita coisa; nmeros
inteiros costumam ser armazenados em espaos de 1, 2, 4 ou 8 bytes (ou seja, 8, 16, 32 ou
64 bits, respectivamente).
Apesar de o tamanho de um inteiro ser padronizado, ainda possvel que existam intei-
ros de mais de um tamanho. Por exemplo, em C existem pelo menos trs tipos inteiros, de
tamanhos diferentes. O que ocorre que cada processador trabalha naturalmente com um
certo tamanho de inteiro (usualmente, 32 ou 64 bits) todos os seus espaos internos de
48 Captulo 4. Mais sobre nmeros
armazenamento (eles se chamam registradores) tm esse mesmo tamanho. Quando quere-
mos usar inteiros de tamanhos diferentes, o processador simplesmente trabalha com pedaos
desses tais registradores ou com vrios registradores juntos.
Agora voltemos um pouco ao C. J conhecemos os dois tipos bsicos de inteiros: int e,
embora no o tenhamos usado ainda, char (como vimos no comeo do primeiro captulo, os
caracteres so armazenados como se fossem inteiros). O tipo char tem um tamanho nico:
um byte ou seja, 8 bits. O tipo int geralmente tem um tamanho padro de 32 bits, mas
ele tambm possui alguns subtipos com tamanhos diferentes. So eles:
short int, um inteiro curto, que costuma ter 16 bits;
long int, um inteiro longo, que costuma ter 32 bits (no h nada de errado aqui; o
tipo int realmente costuma ser igual ao long).
long long int, que geralmente tem 64 bits. (Somente no padro C99.)
Nota: No padro da linguagemC, os tamanhos desses tipos de dados so denidos de uma maneira
mais formal (e mais vaga), devido grande diferena entre os vrios tipos de sistemas em que o C
pode ser usado. Eu os deni de uma maneira mais prtica, segundo os tamanhos que esses tipos
tm na maioria dos computadores pessoais de hoje em dia; uma denio mais correta pode ser
encontrada na especicao da linguagem.
Todos esses subtipos podem (e costumam) ser abreviados, tirando deles a palavra int.
Assim, short int costuma ser escrito como simplesmente short (e assim por diante).
Quo grandes so esses tamanhos? Que valores cabem num inteiro de 32 ou 16 bits
(ou de qualquer outro nmero n)? Para isso, podemos pensar no menor e no maior nmero
que pode ser representado com n bits. O resultado anlogo ao caso decimal: se temos k
algarismos, o maior nmero que podemos representar uma sequncia de k algarismos 9,
que igual a 10
k
1. Da mesma maneira, com n bits, o maior nmero representvel (em
binrio) 2
n
1, que corresponde a uma sequncia de n algarismos 1.
Assim, com 32 bits, por exemplo, podemos representar todos os nmeros de 0 a 2
32
1,
que vale 4 294 967 295. Na tabela a seguir voc pode ver as capacidades dos tamanhos de
inteiros mais comuns.
Tabela 4.1: Os tamanhos mais comuns de inteiros nos microcomputadores atuais, e os maiores n-
meros armazenveis com tais tamanhos.
Tipo Bits Bytes Maior nmero
char 8 1 255
short 16 2 65 535
int, long 32 4 4 294 967 295
long long 64 8 18 446 744 073 709 551 615
4.2.1 Nmeros negativos
Talvez no meio disso tudo voc tenha se perguntado se existe alguma maneira de escrever
nmeros negativos no sistema binrio. Numa escrita humana, o natural seria simplesmente
Voc tambm pode chegar a esses nmeros pensando em termos das nossas somas
a
k
b
k
, com todos os
a
k
= b 1. Lembre da frmula de soma de uma progresso geomtrica e verique que, de fato, o resultado o
mesmo.
O armazenamento dos dados 49
escrever o sinal de menos para os nmeros negativos. Nos computadores, a codicao mais
comumde nmeros negativos temcomo princpio reservar umdos bits do nmero para o sinal
assim, umnmero de 32 bits ca com31 bits para guardar o mdulo do nmero e 1 bit para
guardar o sinal. Geralmente usado o bit mais signicativo (o que estiver mais esquerda na
nossa representao usual), e ele vale 1 para nmeros negativos ou 0 para nmeros positivos.
Sabendo isso, talvez voc poderia imaginar que, por exemplo, se o nmero 14 armazenado
em 8 bits como 0000 1110, o nmero 14 seria armazenado como 1000 1110. No bem
assim.
O que acontece que essa representao no muito eciente quando o computador
vai fazer clculos. Existe uma outra representao, conhecida como representao do com-
plemento de 2, que permite que nmeros negativos sejam operados exatamente da mesma
maneira que os nmeros positivos, isto , sem que o computador precise vericar se um
nmero negativo para saber como fazer a conta.
Complemento de 2 no o melhor nome (seria mais adequado complemento de 2
n
);
mas o mtodo consiste emguardar cada nmero negativo k como se fosse o nmero positivo
2
n
k, sendo n o nmero de bits reservado para o inteiro. Por exemplo, ao trabalhar com
8 bits, o nmero 14 seria guardado como se fosse 256 14 = 242 (em binrio, isso seria
1111 0010).
Agora, se olharmos para o nmero 128, veremos que ele o seu prprio complementar
quando usamos n = 8 bits: 256 128 = 128. Mas lembre-se que a representao binria
de 128 1000 0000 como o bit de sinal 1, faz muito mais sentido convencionar que
essa ser a representao do nmero negativo 128. Ento, nessa representao, o 128 no
existe (precisaramos usar mais que 8 bits); os nmeros positivos paramno 127. Os nmeros
negativos vo at o 128.
Generalizando essas concluses, num espao de n bits possvel guardar, com sinal,
todos os nmeros inteiros de 2
n1
at 2
n1
1. (Veja como isso equivalente ao nosso
intervalo de 128 a 127 para a representao de 8 bits.)
Um algoritmo prtico para calcular o complemento de 2 de um nmero, j na represen-
tao binria, o seguinte: preencha com zeros esquerda para completar a quantidade de
bits que est sendo usada; ento inverta todos os bits da representao (trocar os 0 por 1 e
vice-versa) e some 1. Lembre-se de inverter tambm os zeros esquerda que voc incluiu.
Em C possvel armazenar tanto nmeros negativos quanto positivos, como j mencio-
namos no comeo. Mais ainda, ao declarar uma varivel voc pode escolher se quer guardar
nmeros negativos e positivos (com sinal) ou s positivos (sem sinal) como acabamos de
ver, isso faz diferena no intervalo de nmeros que pode ser guardado. Assim, uma varivel
de 8 bits pode suportar nmeros de 0 a 255 ou nmeros de 128 a 127. Para indicar isso,
voc pode modicar os tipos numricos inteiros (char e int) com mais dois adjetivos: sig-
ned (com sinal) e unsigned (sem sinal). Quando voc no diz nada, o computador assume
que voc quer guardar os nmeros com sinal (signed).
Se voc quiser vericar por que os dois mtodos so equivalentes, pense na soma de um nmero com o que
tem todos os seus bits invertidos.
50 Captulo 4. Mais sobre nmeros
Tabela 4.2: As faixas de nmeros armazenveis nos tipos inteiros com sinal.
Tipo Regio
char (8 bits) 128 a 127
short (16 bits) 32 768 a 32 767
int, long (32 bits) 2 147 483 648 a 2 147 483 647
long long (64 bits) 9 223 372 036 854 775 808 a 9 223 372 036 854 775 807
4.3 Nmeros reais
possvel fazer muita coisa s com nmeros inteiros, mas em muitas situaes somos obri-
gados a usar nmeros fracionrios ou reais (nmeros comvrgula). EmC, os nmeros reais
esto encarnados em dois tipos de dados: oat e double. Os dois funcionam mais ou menos
do mesmo jeito; a diferena entre eles a preciso de cada um dizemos que o oat tem
preciso simples e o double tem preciso dupla. Na maioria das linguagens de programao
(inclusive C), esses tipos de nmeros so conhecidos como nmeros de ponto utuante
mais adiante veremos o porqu desse nome.
Falamos em preciso porque, assim como no caso dos inteiros, necessrio impor um
tamanho para cada nmero, o que limita a representao dos nmeros veremos mais
adiante como exatamente isso se d.
Antes de entrar na parte mais terica, vamos ver como se usam os tais nmeros de ponto
utuante. A declarao de variveis funciona da mesma maneira que para os inteiros:
double x, y;
float z, w;
Para escrever nmeros fracionrios em C, usamos o ponto (.) para separar a parte inteira
da fracionria por exemplo, 3.14159 ou -0.001. Podemos tambm usar uma espcie de
notao cientca para trabalhar com nmeros muito grandes ou muito pequenos escreve-
mos o valor principal do nmero da maneira que acabei de descrever, e usamos a letra e ou
E para indicar a potncia de 10 pela qual o nmero deve ser multiplicado. Alguns exemplos:
3.14159e-7
(
3,14159 10
7
)
1.234E+26
(
1,234 10
26
)
4.56e5
(
4,56 10
5
)
Veja que no faz diferena se o caractere E maisculo ou minsculo, nem se colocamos o
sinal de + nos expoentes positivos.
To importante quanto saber escrever esses nmeros saber imprimi-los na tela ou l-
los do teclado. Usando a funo printf, usamos o cdigo %f (da mesma maneira que o nosso
conhecido %d), tanto para o double quanto para o oat, como no seguinte exemplo:
#include <stdio.h>
int main()
{
double x = 5.0;
float y = 0.1;
int z = 27;
printf("x = %f\n", x);
Nmeros reais 51
printf("y = %f, z = %d\n", y, z);
return 0;
}
Para ler nmeros reais do teclado, precisamos dizer se a varivel onde vamos guardar
do tipo double ou oat. No primeiro caso, usamos o cdigo %lf; no segundo, apenas %f
(como no printf)
#include <stdio.h>
int main()
{
double x, z;
float y;
printf("Digite dois nmeros de ponto flutuante: ");
scanf("%lf %f", &x, &y);
z = x + y;
printf("%f\n", z);
return 0;
}
Veja que podemos fazer contas entre nmeros de ponto utuante da mesma maneira que
fazamos com os inteiros; podemos at misturar doubles com oats. Mais adiante faremos
algumas observaes sobre isso.
4.3.1 Mais sobre printf
Quando trabalhamos com nmeros de ponto utuante, pode ser til exibir nmeros em no-
tao cientca. Para isso, podemos trocar nosso cdigo %f por %e ou %E: o resultado ser
impresso com a notao que estabelecemos anteriormente, usando um E maisculo ou mi-
nsculo dependendo do que for especicado. Por exemplo,
printf("%e %E\n", 6.0, 0.05);
/* resultado: 6.000000e+00 5.000000E-02 */
Mas o printf tambmtemuma opo muito inteligente, que a %g ela escolhe o melhor
formato entre %f e %e de acordo com a magnitude do nmero. Nmeros inteiros no muito
grandes so impressos sem casas decimais (e sem ponto); nmeros muito grandes ou muito
pequenos so impressos com notao cientca. Tambm podemos usar um G maisculo se
quisermos que o E da notao cientca saia maisculo. Por exemplo:
printf("%g %g %G\n", 6.0, 0.00005, 4000000.0);
/* resultado: 6 5e-05 4E+06 */
Outra coisa que podemos mudar o nmero de casas decimais exibidas aps o ponto. O
padro para o formato %f de 6 casas; isso pode ser demais em vrios casos por exemplo,
52 Captulo 4. Mais sobre nmeros
se voc for imprimir preos de produtos. Para que o nmero de casas decimais exibidas seja
n, trocamos o cdigo %f por
%.nf
Para imprimirmos um nmero com 2 casas, por exemplo, podemos escrever assim:
printf("Preo = %.2f\n", 26.5);
/* resultado: 26.50 */
Se colocarmos n = 0, o ponto decimal tambm ser apagado. Veja que os nmeros
sero sempre arredondados conforme necessrio. Por exemplo:
printf("%.2f %.0f\n", 26.575, 26.575);
/* resultado: 26.58 27 */
4.3.2 A representao
Falamos um pouco acima que tanto o oat quanto o double so tipos de ponto utuante.
Isso diz respeito representao dos nmeros reais na memria do computador trata-se
de uma espcie de notao cientca. Nessa representao, um nmero tem duas partes: a
mantissa, que so os algarismos signicativos, e o expoente, que indica a posio da vrgula
decimal em relao aos algarismos signicativos.
Pensando na base decimal, umnmero como 0,000395 teria a representao 3,9510
4
,
cuja mantissa 3,95 e cujo expoente 4 (indica que a vrgula deve ser deslocada 4 dgitos
para a esquerda). Na notao cientca, estabelecemos que a mantissa deve ser maior que
ou igual a 1, mas no pode ter mais de um dgito esquerda da vrgula por exemplo, a
mantissa pode ser 1,00 ou 9,99, mas no 15,07 ou 0,03. Assim, estamos de fato dividindo
a informao do nmero em duas partes: o expoente d a ordem de grandeza do nmero, e
a mantissa d o valor real dentro dessa ordem de grandeza.
Nem todo nmero tem uma representao decimal nita em outras palavras, nem
todo nmero tem uma mantissa nita. Por exemplo, a frao 5/3 pode ser expandida inni-
tamente como 1,6666 . . .; se quisermos express-la com um nmero nito de dgitos (como
necessrio em um computador), devemos parar em algum dgito e arredondar conforme
necessrio por exemplo, 1,6667, se precisarmos trabalhar com 5 dgitos.
Assim, para colocar um nmero nessa representao, necessrio primeiro normalizar
a mantissa para que que entre 1 (inclusive) e 10, e depois arredond-la para um nmero de
dgitos pr-estabelecido.
A representao usual de ponto utuante no computador praticamente igual a essa;
antes de ver como ela realmente funciona, vamos ver como funciona a representao dos
nmeros fracionrios na base binria.
O que a representao decimal de um nmero? Considere um nmero que pode ser
escrito como a
n
a
n1
a
1
a
0
,b
1
b
2
(veja que a parte inteira, esquerda da vrgula,
tem um nmero nito de dgitos; a parte fracionria, direita da vrgula, pode ter innitos
dgitos). J vimos como funciona a representao da parte inteira; falta analisarmos a parte
fracionria.
Vejamos primeiro o caso de um nmero cuja parte fracionria nita e tem m dgitos
no vamos nos importar com a parte inteira; suponha que zero. Esse nmero pode ser
escrito como 0,b
1
b
2
b
m
. Ento, se multiplicarmos esse nmero por 10
m
, ele voltar
Nmeros reais 53
a ser inteiro, e ser escrito como b
1
b
2
b
m
. J sabemos como funciona a representao
inteira de um nmero! Esse nmero vale
b
1
10
m1
+ b
2
10
m2
+ + b
m1
10
1
+ b
m
10
0
=
m
k=1
b
k
10
mk
Como esse o nmero original multiplicado por 10
m
, o nmero original vale
b
1
10
1
+ b
2
10
2
+ + b
m1
10
(m1)
+ b
m
10
m
=
m
k=1
b
k
10
k
No difcil estender essas contas para os casos em que a parte fracionria no nita
(ou em que a parte inteira no zero), mas seria muito tedioso reproduzi-las aqui; deix-las-
ei como exerccio se voc quiser pensar um pouco, e direi apenas que aquela representao
(possivelmente innita) a
n
a
n1
a
1
a
0
,b
1
b
2
corresponde ao nmero
n
k=0
a
k
10
k
+
k=1
b
k
10
k
Como no caso dos inteiros, claro que todos os dgitos a
k
e b
k
s podem assumir os va-
lores {0, 1, . . . , 9}, para que a representao de cada nmero seja nica. (Na verdade isso
no suciente para que a representao seja nica; precisamos, para isso, exigir que haja
um nmero innito de dgitos diferentes de 9, evitando assim as representaes do tipo
0,999999 . . . 1.)
Veja que essa representao uma extenso da representao dos inteiros; poderamos
trocar os bs por as comndices negativos (b
k
= a
k
) e estender a somatria para os ndices
negativos:
n
k=
a
k
10
k
Com isso, a transio para a base binria est muito bem encaminhada. O que mudar na
base binria que os dgitos so multiplicados por potncias de 2 (o dgito a
k
multiplicado
por 2
k
), e s podem assumir os valores 0 e 1.
Por exemplo, o nmero 3/8 = 1/8 + 1/4 = 2
2
+ 2
3
poder ser representado como
(0,011)
2
. E o nmero 1/10, que aparentemente muito simples? Na base 2, ele no tem
uma representao nita! fcil ver o porqu: se ele tivesse representao nita, bastaria
multiplicar por uma potncia de 2 para torn-lo inteiro; sabemos que isso no verdade
(nenhuma potncia de 2 divisvel por 10!).
Como descobrir a representao binria de 1/10? Vou falar sobre dois jeitos de fazer
isso. O primeiro envolve alguns truquezinhos algbricos. Vamos escrever
1
10
=
1
2
1
5
=
1
2
3
15
=
3
2
1
2
4
1
=
3
2
2
4
1
1 2
4
Agora a ltima frao a soma da srie geomtrica de razo 2
4
. Podemos ento escrever
1
10
= 3 2
5
(
1 + 2
4
+ 2
8
+
)
= (1 + 2) 2
5
(
1 + 2
4
+ 2
8
+
)
=
(
2
4
+ 2
5
)
(
1 + 2
4
+ 2
8
+
)
1
10
=
(
2
4
+ 2
8
+ 2
12
+
)
+
(
2
5
+ 2
9
+ 2
13
+
)
54 Captulo 4. Mais sobre nmeros
Assim, identicando esse resultado com a frmula da representao de um nmero, temos
b
k
= 1 se k = 4, 8, 12, . . . ou se k = 5, 9, 13, . . . , e b
k
= 0 caso contrrio. Ou seja,
os dgitos nas posies 4, 5, 8, 9, 12, 13, . . . so 1, e os outros so 0. Logo, a representao
binria de 1/10
1
10
= (0,0 0011 0011 0011 . . .)
2
Apartir dessas contas, podemos ver alguns padres interessantes (emanalogia coma base
10). Um deles que multiplicar e dividir por 2 tem, na base binria, o mesmo efeito que
tinham as multiplicaes/divises por 10 na base decimal. Ou seja, ao multiplicar por 2 um
nmero, a vrgula vai uma posio para a direita na representao binria (ou acrescenta-se
um zero caso o nmero seja inteiro).
Outro padro que fraes do tipo
a
2
n
1
, em que a < 2
n
1 um inteiro, so timas
geradoras de dzimas peridicas (binrias): podemos escrev-las como
a
2
n
1
=
a
2
n
1
1 2
n
=
a
2
n
(
1 + 2
n
+ 2
2n
+
)
Como a representao de a tem no mximo n dgitos, e a srie geomtrica direita uma
dzima de perodo n, a representao da nossa frao ser uma dzima cujo perodo a
representao de a (com zeros esquerda para completar os n dgitos se necessrio).
O outro mtodo de achar a representao decimal de uma frao voc j conhece:
o algoritmo da diviso. Para aplic-lo ao caso das fraes, basta escrever o denominador
e o numerador em binrio e fazer o processo de diviso, lembrando que as regras do jogo
mudam um pouco por exemplo, voc s pode multiplicar o divisor por 0 ou 1. No vou
ensinar os detalhes aqui no o propsito deste livro. Mas basicamente este o algoritmo
usado pelo computador para converter nmeros fracionrios para a representao binria;
assim que ele pode controlar quantos dgitos sero mantidos na representao basta parar
o algoritmo na casa desejada.
Fizemos uma longa digresso sobre representao binria de nmeros. Para que isso ser-
viu? Agora poderemos entender melhor como funciona a representao de ponto utuante.
Como dissemos acima, na representao de ponto utuante um nmero tem duas partes: a
mantissa e o expoente. Para a mantissa M devemos impor uma condio de normalizao
como no caso da notao cientca decimal: devemos ter 1 M < 2 para que esquerda
da vrgula haja um e apenas um dgito (no-nulo). Assim, uma frao como 21/4 seria
normalizada da seguinte maneira:
21
4
=
21
16
4 =
(10101)
2
2
4
2
2
= (1,0101)
2
2
2
Devemos considerar que a mantissa deve ser representada comumtamanho xo (e nito)
de dgitos. No caso da preciso simples do oat, esse nmero costuma ser de 24 dgitos.
Assim, muitas fraes precisamser arredondadas tanto as que tmrepresentaes innitas
quanto as que tm representaes nitas porm muito longas. Por exemplo, a frao 1/10
(representao innita) seria arredondada para
(1,100 1100 1100 1100 1100 1101)
2
2
4
= 0,100000001490116119384765625
Agora vamos chegar mais perto da representao que realmente usada pelo computador.
Um nmero de ponto utuante representado com a seguinte estrutura:
Nmeros reais 55
.. ..
sinal
31
63
..
expoente
30 23
62 52
..
mantissa
22 0
51 0
Figura 4.1: Esboo da estrutura de representao de ponto utuante. Em cada parte foram indicados
os nmeros dos bits utilizados nas representaes de preciso simples e dupla, respectivamente.
Eu ainda no havia falado do sinal: a representao de ponto utuante usa um bit para o
sinal, assim como na representao de inteiros. Mas, ao contrrio desta, em ponto utuante
no se usa nada parecido com o complemento de 2; a nica diferena entre as representa-
es de dois nmeros de mesma magnitude e sinais opostos o bit de sinal.
O expoente (relacionado a uma potncia de 2, no de 10!) representado (quase) como
um nmero inteiro comum, e por isso h uma limitao nos valores que ele pode assumir.
Na preciso simples, so reservados 8 bits para o expoente, o que permite at 256 valores
possveis no so exatamente de 128 a 127; na verdade, os dois extremos so reservados
para situaes especiais; os expoentes representveis so de 127 at 126. Na preciso dupla,
usam-se 11 bits, e a gama de expoentes muito maior: de 1023 at 1022.
Sobre a mantissa no resta muito a falar; os bits da mantissa so armazenados sequenci-
almente, como na representao binria que construmos acima. Em preciso simples, so
reservados 23 bits para a mantissa. O leitor atento notar que eu falei em 24 dgitos al-
guns pargrafos atrs. De fato, a representao binria s reserva 23 dgitos. Mas, como a
mantissa est sempre entre 1 e 2, esquerda da vrgula temos sempre um algarismo 1 sozi-
nho; ento, convenciona-se que esse algarismo no ser escrito (o que nos deixa com 1 bit
a mais!), e assim temos uma mantissa de 24 dgitos em um espao de apenas 23 bits. Na
preciso dupla, so reservados 52 bits (que na verdade representam 53).
Na verdade, h vrios outros detalhes sobre a representao de ponto utuante; poderia
gastar mais algumas pginas com essa descrio, mas no vem ao caso. Meu objetivo era dar
uma idia geral do funcionamento dessa representao.
At agora, s explorei a parte terica dessa representao. Precisamos conhecer tambm
as caractersticas prticas da representao de ponto utuante. Por exemplo, em termos
da representao decimal, como se traduzem os limites de representao do expoente e da
mantissa? No vou fazer contas aqui, vou apenas mostrar os resultados prticos. Descreverei
primeiro que resultados so esses:
Devido aos limites de expoentes, existe umnmero mximo e umnmero mnimo que
podem ser representados com uma dada preciso. O nmero mximo corresponde ao
maior expoente possvel com a maior mantissa possvel; o nmero mnimo, analoga-
mente, corresponde ao menor expoente possvel com a menor mantissa possvel. Na
tabela, so apresentadas as faixas aproximadas.
Os mesmos limites se aplicam para os mdulos dos nmeros negativos, j que nme-
ros negativos e positivos so tratados de maneira simtrica na representao de ponto
utuante.
Como vrios nmeros precisam ser arredondados para serem representados, a repre-
sentao tem um certo nmero de algarismos signicativos de preciso quantas
casas decimais esto corretas na representao de um nmero. Como os nmeros no
so representados em base 10, a quantidade de casas corretas pode variar de nmero
para nmero; os valores apresentados na tabela seguir correspondem quantidade m-
nima.
56 Captulo 4. Mais sobre nmeros
Tabela 4.3: Comparao dos diferentes tipos de nmero de ponto utuante em C
Tipo Expoente Mantissa Intervalo Preciso
oat (32 bits) 8 bits 23 bits 10
45
10
38
6 decimais
double (64 bits) 11 bits 52 bits 10
324
10
308
15 decimais
long double (80 bits) 15 bits 64 bits 10
4950
10
4932
19 decimais
Nota: A representao de ponto utuante pode no ser a mesma em todos os computadores. As
informaes aqui descritas esto de acordo com a representao conhecida como IEEE 754, que
a mais usada nos computadores pessoais hoje em dia.
O tipo long double ainda no tinha sido mencionado. Ele um tipo que, em geral, tem
preciso maior que o double (embora certos compiladores no sigam essa norma), mas seu
tamanho no to padronizado como os outros dois tipos (preciso simples e dupla). Alm
de 80 bits, possvel encontrar tamanhos de 96 ou 128 bits.
De maneira similar ao double, necessrio utilizar o cdigo %Lf em vez de apenas %f
quando queremos ler comscanf uma varivel longdouble. Nesse caso tambm necessrio
utilizar esse mesmo cdigo para o printf (em contraste com o double que continua usando
o cdigo %f).
4.3.3 Problemas e limitaes
Como j vimos, os tipos de ponto utuante no conseguem representar com exatido todos
os nmeros fracionrios. Devido aos arredondamentos, acontecem alguns problemas que,
segundo a matemtica que conhecemos, so inesperados. Contas que, matematicamente,
do o mesmo resultado, podem ter resultados diferentes. At uma simples troca de ordem
das operaes pode alterar o resultado.
Vamos ilustrar isso com um exemplo. Pediremos ao usurio que digite os coecientes
reais de uma equao quadrtica
ax
2
+ bx + c = 0,
e analisaremos os trs tipos de soluo que podem ocorrer dependendo do valor de =
b
2
4ac (ainda no vimos como calcular raiz quadrada, ento vamos apenas analisar os
casos):
Se > 0, a equao tem duas razes reais distintas.
Se = 0, a equao tem uma raiz real dupla.
Se < 0, a equao no tem razes reais.
O programa caria assim:
#include <stdio.h>
int main()
{
float a, b, c, delta;
printf("D os coeficientes da equao ax^2 + bx + c = 0: ");
scanf("%f %f %f", &a, &b, &c);
Nmeros reais 57
delta = b*b - 4*a*c;
printf("Delta = %g\n", delta);
if (delta > 0)
printf("A equao tem duas razes reais distintas.\n");
else if (delta == 0)
printf("A equao tem uma raiz real dupla.\n");
else
printf("A equao no tem razes reais.\n");
return 0;
}
Esse programa, apesar de estar logicamente correto, apresenta um problema. Considere
as equaes que tm uma raiz dupla , ou seja, equaes do tipo a(x )
2
: para elas,
exatamente igual a 0. No entanto, se voc testar o programa para vrias dessas equaes,
descobrir um comportamento estranho: para muitos valores de , o valor de calculado
pelo programa no igual a zero. Por exemplo, para valores no inteiros e prximos de 1,
obtemos um da ordem de 10
7
.
Por que isso acontece? Oproblema so os arredondamentos que j vimos seremnecess-
rios para representar a maioria dos nmeros; por conta deles, nossa conta no d exatamente
zero no nal.
Neste momento, considere isso como um alerta evite fazer comparaes de igualdade
com nmeros de ponto utuante; um pouco mais adiante, veremos o que possvel fazer
para contornar (ou conviver melhor com) essas limitaes.
4.3.4 Ponto utuante vs. inteiros
As operaes entre nmeros de ponto utuante funcionam basicamente da mesma maneira
que operaes entre inteiros; voc pode at realizar operaes mistas entre nmeros inteiros
e de ponto utuante o inteiro convertido automaticamente para ponto utuante e a
operao realizada como se os dois nmeros fossem de ponto utuante. Em geral, sempre
que voc fornece um inteiro num lugar onde era esperado um nmero de ponto utuante, o
inteiro convertido para ponto utuante.
Por outro lado, a coerncia matemtica da linguagem exige que o resultado de uma ope-
rao entre dois inteiros seja um inteiro. Agora lembremos que a diviso entre dois nmeros
inteiros nem sempre exata, e da surgem os nmeros racionais a diviso entre dois
nmeros inteiros deve ser, em geral, um nmero racional. Como resolver esse conito?
Em C, a primeira regra (fechamento das operaes entre inteiros) a que prevalece:
quando dividimos dois inteiros, obtemos apenas o quociente da diviso inteira entre eles.
Para obter o resultado racional da diviso entre dois inteiros, precisamos converter um deles
em ponto utuante.
Suponha que voc deseja imprimir a expanso decimal da frao 1/7. Uma possvel
primeira tentativa seria a seguinte:
float x;
x = 1/7;
printf("1/7 = %f\n", x);
58 Captulo 4. Mais sobre nmeros
Para sua frustrao, voc obteria o nmero 0.000000 como resultado. Claro, se voc fez
uma diviso entre inteiros, o resultado foi a diviso inteira entre eles; ao guardar o resultado
numa varivel de ponto utuante, apenas o resultado convertido quem decide que tipo
de operao ser realizada so os operandos em si.
Para fazer o que queramos, pelo menos um dos operandos dever ser de ponto utuante.
Ou seja, devemos escolher uma das alternativas:
x = 1.0 / 7;
x = 1 / 7.0;
x = 1.0 / 7.0;
Mas e se quisssemos calcular a diviso entre dois nmeros que no conhecemos a priori?
Devemos usar a
4.3.5 Converso explcita de tipos (casting)
Muitas converses de tipo so feitas automaticamente em C, mas em alguns casos, como na
motivao que antecede esta seo, preciso fazer uma converso forada. Esse tipo de
converso explcita tambm conhecido pelo nome tcnico (em ingls) de casting. Ela feita
da seguinte maneira:
(novo_tipo) varivel_ou_valor
Por exemplo, para calcular a razo (fracionria) entre dois inteiros fornecidos pelo usu-
rio, poderamos converter um deles para o tipo oat e realizar a diviso:
int a, b;
float x;
/* (...) leitura dos nmeros */
x = (float)a / b;
printf("%f\n", x);
Note, porm, que o operador de converso de tipos s atua sobre a varivel que vem
imediatamente depois dele por exemplo, se quisssemos converter uma expresso inteira,
precisaramos de parnteses:
x = (float)(a / b) / c;
Nesse caso, seria realizada a diviso inteira entre a e b, e depois seu quociente seria dividido
por c, numa diviso racional.
4.4 Funes matemticas
Em diversas reas, as quatro operaes bsicas no cobrem todas as contas que precisam ser
feitas em um programa. muito comum, mesmo que no estejamos lidando diretamente
com matemtica, precisar das funes trigonomtricas, raiz quadrada, exponencial, entre
outras. Todas essas funes de uso comum constam no padro da linguagem C e fazem
parte da biblioteca matemtica padro. Para us-las em um programa, precisamos de mais
uma instruo no comeo do arquivo de cdigo:
#include <math.h>
Funes matemticas 59
Ao compilar com o GCC, tambm necessrio incluir a biblioteca matemtica na linha
de comando da compilao; isso feito com a opo -lm. (Na verso do MinGW para
Windows, devido diferena de organizao do sistema, isso no necessrio.)
Uma lista completa das funes disponveis nesse arquivo pode ser facilmente encontrada
nas referncias sobre a biblioteca padro; na tabela 4.4 so listadas apenas as principais. A
maioria das funes aqui listadas recebe apenas um parmetro, exatamente como esperado;
casos excepcionais sero indicados.
Tabela 4.4: Principais funes matemticas da biblioteca padro
Funo Signicado
sin, cos, tan Funes trigonomtricas: seno, cosseno e tangente. Os ngulos
so sempre expressos em radianos.
asin, acos, atan Funes trigonomtricas inversas. asin e atan devolvem um n-
gulo no intervalo
[
2
,
2
]
; acos devolve um ngulo no intervalo
[0, ].
sinh, cosh, tanh Funes hiperblicas (seno, cosseno e tangente)
sqrt Raiz quadrada (square root)
exp Funo exponencial (e
x
)
log Logaritmo natural, base e (ln)
log10 Logaritmo na base 10
abs, fabs Mdulo (valor absoluto) de um nmero. Use abs para inteiros e
fabs para nmeros de ponto utuante.
pow(x, y) Potenciao: x
y
(x e y podem ser nmeros de ponto utuante)
4.4.1 Clculo de sries
Vrias das funes implementadas na biblioteca matemtica podem ser calculadas por meio
de expanses em srie (somatrias innitas); por exemplo, a funo exponencial:
e
x
=
k=0
x
k
k!
= 1 + x +
x
2
2
+
x
3
3!
+
x
4
4!
+
claro que, em um computador, impossvel calcular todos os termos dessa srie para
chegar ao resultado; aps um certo ponto, devido convergncia das sries, o valor de cada
termo muito pequeno, de modo que, frente preciso do computador, som-los no faz
mais diferena. Dessa maneira, devemos somar um nmero nito N de termos dessa srie.
Mas como escolher N? Seguem alguns dos critrios possveis:
Escolher um N xo. Esse critrio no muito bom, porque a preciso at o N-simo
termo costuma depender do argumento x; em vrios casos, para valores grandes de
x necessrio somar um nmero maior de parcelas, enquanto, para x bem pequeno,
poucas parcelas j do uma aproximao muito boa.
Limitar a magnitude das parcelas a serem somadas interromper a soma quando o
mdulo da parcela for menor que um dado (por exemplo, = 10
9
). Esse critrio
melhor que o anterior, mas no leva em conta a magnitude do resultado; por exemplo,
se o valor da funo for da ordem de 10
9
, uma parcela de 10
9
realmente no faz
60 Captulo 4. Mais sobre nmeros
diferena; se o valor da funo for da ordem de 10
6
, a mesma parcela de 10
9
ainda
signicativa.
Podemos modicar um pouco esse critrio levando isso em conta: em vez de limitar
o tamanho de cada parcela, limitaremos o tamanho da parcela dividido pelo valor
acumulado at ento. Se dividirmos pela soma do passo anterior, que no inclui essa
parcela, obtemos a quantidade conhecida como variao relativa se S
n
indica a
soma das primeiras n parcelas, a (n + 1)-sima parcela corresponde a S
n+1
S
n
, e
ento o critrio de limitao na variao relativa traduz-se matematicamente em
S
n+1
S
n
S
n
< ,
parando no passo n + 1 se essa condio for satisfeita. Esse critrio bastante usado
em diversos mtodos numricos.
Devido preciso do computador, em algum momento as parcelas caro to pe-
quenas que absolutamente no iro mais alterar a soma por exemplo, se estamos
trabalhando com 5 algarismos signicativos, somar 0,0001 no nmero 100 no faz
a menor diferena. Assim, podemos comparar a soma de cada passo com a soma
anterior, e interromper o processo quando as somas forem iguais.
No entanto, necessrio tomar cuidado com possveis otimizaes do compilador
como voc somou uma parcela no-nula, logicamente a soma deve ter-se alterado;
portanto, com certas opes de otimizao ativadas, o compilador adianta a resposta
da comparao entre as duas somas e diz que elas so diferentes, sem vericar se a
variao realizada estava realmente dentro da preciso do ponto utuante. O resultado
disso um lao innito.
4.4.2 Denindo constantes
A linguagem C permite que voc crie apelidos para constantes que voc usa no seu pro-
grama. Isso ajuda muito a manter o cdigo organizado, pois evita que o cdigo que cheio
de nmeros annimos difceis de entender. Alm disso, quando utilizamos uma constante
pelo nome, ganhamos uma grande exibilidade: se precisamos alterar o valor dela, podemos
alterar simplesmente a sua denio, e a alterao reetir-se- em todos os lugares onde a
constante foi utilizada.
Para denir constantes, utilizamos o comando #define, da seguinte maneira:
#dene CONSTANTE valor
As restries sobre os nomes das constantes so as mesmas que para nomes de variveis e
funes. Para utilizar essas constantes,
Um exemplo de aplicao disso: suponha que voc criou um programa que mostra o
cardpio de uma pizzaria, recebe um pedido e calcula o valor total. Agora a pizzaria quer
que voc refaa o programa pois os preos foram reajustados. Se voc tiver escrito os pre-
os diretamente no meio do cdigo do programa, voc ter bastante trabalho procurando
os preos e modicando em diversos lugares. No entanto, se voc denir constantes como
PRECO_MARGHERITA e PRECO_PORTUGUESA e us-las no cdigo, voc s precisar alterar a de-
nio das constantes.
O tipo char 61
#include <stdio.h>
#define PRECO_MUSSARELA 15.00
#define PRECO_MARGHERITA 16.00
#define PRECO_PORTUGUESA 18.50
void imprime_cardapio()
{
printf (
"Mussarela %5.2f\n"
"Margherita %5.2f\n"
"Portuguesa %5.2f\n",
PRECO_MUSSARELA, PRECO_MARGHERITA, PRECO_PORTUGUESA);
}
/* Deixarei como exerccio para voc pensar a parte de registrar um
* pedido. Na verdade, por enquanto voc pode se preocupar apenas em
* calcular o preo total.
* Sugesto: use um terminador para poder saber quando o pedido acaba.
*/
4.5 Otipo char
Voc j conheceu trs dos quatro tipos fundamentais da linguagem C. Falta apenas um, o
tipo char que j mencionamos, mas no tivemos a oportunidade de usar. Como o nome
sugere, ele designado para guardar caracteres (como a Y ^ { @ # %). No entanto, ele
apenas mais um tipo inteiro, com um intervalo de valores permitidos mais modesto que
o de um inteiro comum. Isso ocorre assim porque, para um computador, um caractere
apenas um nmero; caracteres so codicados como nmeros de acordo com uma tabela de
correspondncia por exemplo, a letra A maiscula armazenada como 65, o cifro $ como
36, etc. Esses exemplos so os cdigos da conhecida tabela ASCII, que a base do padro
atual para a codicao de caracteres.
Uma varivel do tipo char pode conter um caractere (apenas um!), e caracteres so re-
presentados entre aspas simples (no confunda com as aspas duplas como as que voc usa
nas funes printf e scanf). Alguns exemplos: 'a', '2', '@'. Um exemplo de varivel do
tipo char seria:
char opcao;
opcao = 'b';
H alguns caracteres que no so representados literalmente, mas por meio de cdigos
especiais. Um deles j lhe foi apresentado, a saber, o caractere de quebra de linha, repre-
sentado pela sequncia '\n'. Alguns outros exemplos so apresentados na tabela 4.5, junto
com os cdigos numricos correspondentes na tabela ASCII.
62 Captulo 4. Mais sobre nmeros
Tabela 4.5: Algumas das sequncias utilizadas para representar caracteres especiais.
Sequncia ASCII Signicado
\t 9 Tabulao. Avana o cursor para posies pr-denidas ao longo
da linha; usualmente, essas posies so denidas de 8 em 8 ca-
racteres a partir da primeira posio da linha.
\n 10 Quebra de linha. Esse caractere comumente conhecido pela sigla
NL, new line, ou por LF, line feed.
\r 13 Retorno de carro (CR, carriage return): retorna o cursor para o
incio da margem esquerda. (Ver observao adiante.)
\" 34 Aspa dupla.
\' 39 Aspa simples.
\\ 92 Barra invertida.
\nnn Permite especicar qualquer caractere pelo seu cdigo em base
octal (de 000 a 377). Cada n um dgito de 0 a 7.
\xnn Idem, em base hexadecimal (de 00 a FF). Cada n um dgito de
0 a 9 ou uma letra de A a F (maiscula ou minscula).
Os caracteres CR e LF (alm de outros que no foram indicados aqui) so uma herana
da poca das mquinas de escrever. Para comear a escrever texto na linha seguinte, eram
necessrias duas aes: retroceder o carro de impresso para o comeo da margem (CR)
e alimentar mais uma linha de papel (LF). Em sistemas Windows, o nal de uma linha de
texto usualmente indicado pela sequncia CR + LF, ao invs de simplesmente LF, como
de praxe nos sistemas Unix (enquadram-se aqui o Linux e o Mac OS X). Nos sistemas Mac
antigos usava-se apenas CR, sem LF.
Veja que os caracteres " ' \ precisam de uma representao alternativa: as duas aspas
porque podemos querer represent-las como caracteres sem que sejam interpretadas como
o nal da sequncia de caracteres; a barra invertida porque ela prpria usada em outras
sequncias de caracteres especiais.
4.5.1 Entrada e sada
Para ler e imprimir variveis do tipo char, voc pode usar o cdigo de formato %c nas funes
printf e scanf. Por exemplo:
char opcao;
scanf("%c", &opcao);
printf("Voc escolheu a opo (%c)!\n", opcao);
int opcao;
opcao = getchar();
putchar('a');
importante notar que, tanto com a funo scanf quanto com a getchar, os caracteres
digitados no so recebidos pelo programa em tempo real. Cada vez que voc digita um
caractere, ele armazenado temporariamente numa regio da memria chamada buer de
teclado, e s aps o nal da linha que o contedo do buer liberado para o nosso programa,
atravs dessas funes.
5
Ponteiros e vetores
5.1 Prolegmenos
Num computador, cada varivel guardada em uma certa posio da memria. Essas posi-
es de memria so numeradas, de modo que cada uma tem um endereo numrico
como se cada uma fosse uma casa em uma rua.
Em C, um ponteiro (tambm chamado de apontador) uma varivel que guarda uma
referncia a outra varivel seu valor o endereo de uma outra varivel. Se o valor de um
ponteiro p o endereo de uma varivel X, ento dizemos que p aponta para X. Veja uma
ilustrao disso na gura 5.1. Se um ponteiro aponta para uma varivel, voc pode acessar
essa varivel (ler ou alterar seu valor) atravs do ponteiro logo veremos como.
Figura 5.1: Esquema do funcionamento de um ponteiro. O ponteiro p contm o endereo da varivel
n (1032), ou seja, p aponta para n.
Isso pode parecer apenas uma maneira de complicar as coisas, mas na realidade tem
diversas utilidades, das quais citamos algumas:
Quando precisamos transmitir uma grande quantidade de dados a outra parte do pro-
grama, podemos passar apenas um ponteiro para esses dados em vez de fazer uma
cpia dos dados e transmitir a cpia. Isso economiza tempo o processo de duplicar
os dados gasta tempo de processamento e, obviamente, espao na memria.
Uma funo em C s pode devolver um valor com a instruo return. No entanto,
se uma funo recebe como parmetros ponteiros para outras variveis, voc poder
gravar valores nessas variveis, e com isso uma funo pode gerar vrios valores de
sada.
63
64 Captulo 5. Ponteiros e vetores
O conceito de ponteiros pode ser estendido para funes possvel passar um pon-
teiro para uma funo como parmetro. Por exemplo, podemos criar uma funo
chamada acha_raiz com um algoritmo numrico que acha razes de uma funo ma-
temtica f; a funo f poderia ser passada (na forma de ponteiro) como parmetro da
funo acha_raiz.
5.1.1 Declarao de ponteiros
Em C, para declarar uma varivel que funciona como ponteiro, colocamos um asterisco (*)
antes do seu nome. Um ponteiro s pode apontar para um tipo de varivel, j que a maneira
de armazenar o valor de uma varivel na memria depende do seu tipo. Por exemplo, um
ponteiro que aponta para uma varivel inteira no pode ser usado para apontar para uma
varivel de ponto utuante. Assim, um ponteiro tambm precisa de um tipo, que deve ser
igual ao tipo de varivel para a qual ele ir apontar.
Por exemplo, se queremos criar um ponteiro p que ir apontar para uma varivel inteira,
declaramo-no da seguinte maneira:
int *p;
Essa instruo apenas declara um ponteiro, sem apont-lo para nenhuma varivel.
Se quisermos declarar vrios ponteiros com uma nica instruo, devemos colocar o
asterisco em cada um deles. Se voc escrever o asterisco apenas no primeiro nome, apenas a
primeira varivel ser um ponteiro!
int *p1, *p2, *p3; /* ok, os trs so ponteiros */
double *p4, p5, p6; /* problema! s p4 ser um ponteiro */
Para fazer um ponteiro apontar para uma varivel, devemos atribuir-lhe como valor o
endereo de outra varivel, e no um nmero comum. Para isso, usamos o operador &
(E comercial), que fornece o endereo de uma varivel aqui ele ser conhecido como o
operador endereo-de. Se temos uma varivel var, seu endereo representado por &var.
Por exemplo:
int n;
int *p;
p = &n;
Aqui criamos uma varivel inteira chamada n, e em seguida criamos um ponteiro p, que
apontado para a varivel n.
5.1.2 Acesso indireto por ponteiros
Para acessar a varivel que apontada por um ponteiro, usamos o operador * (o mesmo
asterisco usado na declarao), chamado operador de indireo ou operador de desreferenci-
ao. Esse operador faz a volta do processo que leva da varivel ao ponteiro (a referencia-
o da varivel); por isso o chamamos de operador de desreferenciao. O nome indireo
usado simplesmente porque isso um acesso indireto varivel.
Esse um termo difcil de se traduzir. Em ingls, diz-se dereference, que seria uma combinao do prexo
de- (equivalente, nesse caso, ao nosso des-) e da palavra que signica referncia, no sentido de desfazer uma
referncia. Muitas pessoas tentam traduzir isso como de-referncia, mas essa palavra no consta nos dicionrios.
(Tudo bem, desreferenciao tambm no, mas eu achei melhor.)
Prolegmenos 65
Nomenclaturas parte, se p um ponteiro, podemos acessar a varivel para a qual ele
aponta com *p. Essa expresso pode ser usada tanto para ler o contedo da varivel quando
para alter-lo.
var p
&var
*p