Professional Documents
Culture Documents
k=0
a
k
b
k
Para que a representaao de umnmero seja nica, necessario que os valores de todos
os seus algarismos sejam menores do que b, ou seja, no maximo b 1. Caso contrario, o
nmero b
2
, por exemplo, poderia ser representado pelas duas sequncias (b, 0) e (1, 0, 0).
Os valores dos dgitos tambm nao podem ser negativos senao surgiria mais uma am-
biguidade na representaao.
E importante nesse ponto nao confundir os valores dos dgitos com as suas representa-
es (que costumam ser os algarismos arabicos sempre que possvel). Aqui, os a
j
simbo-
lizam os valores dos dgitos (como objetos matematicos abstratos), independentemente de
sua representaao. Quando a base nao ultrapassa 10, os valores confundem-se com suas
representaes (usamos os algarismos de 0 a b1 para representar os valores de 0 a b1).
Quando a base maior que 10, precisamos de outra convenao para a base hexadecimal,
por exemplo, os dgitos com valores de 10 a 13 sao representados pelas seis primeiras letras
do alfabeto, enquanto os dgitos at 9 continuam sendo representados da maneira usual.
O armazenamento dos dados 49
Aformula acima nos permite, dados os valores dos dgitos de umnmero, achar o valor
do nmero. Podemos tambm querer realizar o processo inverso: dado um nmero, achar
os valores dos seus dgitos na representaao em base b. Olhando para a formula 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 proximo dgito, a
1
(que pode muito bem ser
zero). Nao difcil concluir que, se realizarmos n + 1 divises por b, os restos serao, 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 divisao, obtemos a
n
como resto e zero como quociente.
Para ilustrar esse algoritmo, vamos fazer uma funao que recebe um nmero inteiro e
imprime seus dgitos na representaao binaria.
void imprimebinorio(inl numero}
{
Whiie (numero > 0} {
prinlf("xd" numero x Z}
numero /= Z
]
prinlf("`n"}
]
Notemos, primeiro, que essa funao fornece os dgitos na ordem inversa (conforme foi
observado acima); por exemplo, o nmero 12 = (1100)
2
seria impresso como 0011.
Veja tambmque, emvez de fxar o nmero de divises, estabelecemos como critrio de
parada o anulamento do quociente. Com isso, nao precisamos saber de antemao quantos
dgitos binarios tem o nmero; vimos que o quociente sera zero quando so sobrar o dgito
mais signifcativo.
4.2 Oarmazenamento dos dados
Ao armazenar um nmero na memoria do computador, precisamos estabelecer um tama-
nho padronizado (uma espcie de nmero de algarismos) que um nmero ocupara. Por
que isso: Imagine que a memoria do computador uma enorme aglomeraao de rodinhas
numeradas. Se nao tivesse umtamanho padrao, como saberamos onde comea e onde ter-
mina cada nmero: Precisaramos de algum outro nmero para saber onde cada nmero
comearia, e essa historia nao terminaria nunca.
Num computador, a menor unidade possvel de informaao o bit; mas a menor uni-
dade de informaao que pode ser de fato acessada o byte, que, nos microcomputadores
modernos, equivale a 8 bits. No entanto, um byte sozinho nao serve para muita coisa; n-
meros 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 in-
teiros de mais de um tamanho. Por exemplo, em C existem pelo menos trs tipos inteiros,
30 Captulo 4. Mais sobre nmeros
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 inter-
nos de armazenamento (eles se chamam registradores) tm esse mesmo tamanho. Quando
queremos usar inteiros de tamanhos diferentes, o processador simplesmente trabalha com
pedaos desses tais registradores ou com varios registradores juntos.
Agora voltemos um pouco ao C. Ja conhecemos os dois tipos basicos de inteiros: inl e,
embora nao o tenhamos usado ainda, chor (como vimos no comeo do primeiro captulo,
os caracteres sao armazenados como se fossem inteiros). O tipo chor tem um tamanho
nico: um byte ou seja, 8 bits. O tipo inl geralmente tem um tamanho padrao de 32
bits, mas ele tambm possui alguns subtipos com tamanhos diferentes. Sao eles:
shorl inl, um inteiro curto, que costuma ter 16 bits;
iong inl, um inteiro longo, que costuma ter 32 bits (nao ha nada de errado aqui;
o tipo inl realmente costuma ser igual ao iong).
iong iong inl, que geralmente tem 64 bits. (Somente no padrao C99.)
Nota: No padrao da linguagem C, os tamanhos desses tipos de dados sao defnidos de uma ma-
neira mais formal (e mais vaga), devido grande diferena entre os varios tipos de sistemas em
que o C pode ser usado. Eu os defni de uma maneira mais pratica, segundo os tamanhos que
esses tipos tmna maioria dos computadores pessoais de hoje emdia; uma defniao mais correta
pode ser encontrada na especifcaao da linguagem.
Todos esses subtipos podem (e costumam) ser abreviados, tirando deles a palavra inl.
Assim, shorl inl costuma ser escrito como simplesmente shorl (e assim por diante).
Quao grandes sao esses tamanhos: Que valores cabemnuminteiro 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 analogo 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 representavel (em
binario) 2
n
1, que corresponde a uma sequncia de n algarismos 1.
Assim, com32 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
nmeros armazenaveis com tais tamanhos.
Tipo Bits Bytes Maior nmero
chor 8 1 255
shorl 16 2 65 535
inl, iong 32 4 4 294 967 295
iong iong 64 8 18 446 744 073 709 551 615
Voc tambm pode chegar a esses nmeros pensando em termos das nossas somas
a
k
b
k
, com todos
os a
k
= b1. Lembre da frmula de soma de uma progresso geomtrica e verique que, de fato, o resultado
o mesmo.
O armazenamento dos dados 31
4.2.1 Nmeros negativos
Talvez no meio disso tudo voc tenha se perguntado se existe alguma maneira de escrever
nmeros negativos no sistema binario. Numa escrita humana, o natural seria simples-
mente escrever o sinal de menos para os nmeros negativos. Nos computadores, a codi-
fcaao mais comum de nmeros negativos tem como princpio reservar um dos bits do
nmero para o sinal assim, um nmero de 32 bits fca com 31 bits para guardar o mo-
dulo do nmero e 1 bit para guardar o sinal. Geralmente usado o bit mais signifcativo
(o que estiver mais esquerda na nossa representaao usual), e ele vale 1 para nmeros ne-
gativos 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. Nao bem assim.
O que acontece que essa representaao nao muito efciente quando o computador
vai fazer calculos. Existe uma outra representaao, conhecida como representaao 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 verifcar se um
nmero negativo para saber como fazer a conta.
Complemento de 2 nao o melhor nome (seria mais adequado complemento de 2
n
);
mas o mtodo consiste em guardar 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
binario, isso seria 1111 0010).
Agora, se olharmos para o nmero 128, veremos que ele o seu proprio complementar
quando usamos n = 8 bits: 256 128 = 128. Mas lembre-se que a representaao binaria
de 128 1000 0000 como o bit de sinal 1, faz muito mais sentido convencionar que
essa sera a representaao do nmero negativo 128. Entao, nessa representaao, o 128
nao existe (precisaramos usar mais que 8 bits); os nmeros positivos param no 127. Os
nmeros negativos vao 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 representaao de 8 bits.)
Umalgoritmo pratico para calcular o complemento de 2 de umnmero, ja na represen-
taao binaria, o seguinte: preencha com zeros esquerda para completar a quantidade de
bits que esta sendo usada; entao inverta todos os bits da representaao (trocar os 0 por 1 e
vice-versa) e some 1. Lembre-se de inverter tambm os zeros esquerda que voc incluiu.
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
Nao difcil estender essas contas para os casos em que a parte fracionaria nao fnita
(ouemque a parte inteira nao zero), mas seria muito tedioso reproduzi-las aqui; deixa-las-
ei como exerccio se voc quiser pensar um pouco, e direi apenas que aquela representaao
(possivelmente infnita) 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
so podem assumir os
valores {0, 1, . . . , 9}, para que a representaao de cada nmero seja nica. (Na verdade isso
nao sufciente para que a representaao 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 representaao uma extensao da representaao dos inteiros; poderamos
trocar os bs por as com ndices negativos (b
k
= a
k
) e estender a somatoria para os
ndices negativos:
n
k=
a
k
10
k
Com isso, a transiao para a base binaria esta muito bem encaminhada. O que mu-
dara na base binaria que os dgitos sao multiplicados por potncias de 2 (o dgito a
k
multiplicado por 2
k
), e so podem assumir os valores 0 e 1.
Por exemplo, o nmero 3/8 = 1/8 +1/4 = 2
2
+2
3
podera ser representado como
(0,011)
2
. E o nmero 1/10, que aparentemente muito simples: Na base 2, ele nao tem
uma representaao fnita! E facil ver o porqu: se ele tivesse representaao fnita, bastaria
multiplicar por uma potncia de 2 para torna-lo inteiro; sabemos que isso nao verdade
(nenhuma potncia de 2 divisvel por 10!).
Como descobrir a representaao binaria 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
36 Captulo 4. Mais sobre nmeros
Agora a ltima fraao a soma da srie geomtrica de razao 2
4
. Podemos entao 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
+
)
Assim, identifcando esse resultado com a formula da representaao de um nmero, te-
mos b
k
= 1 se k = 4, 8, 12, . . . ou se k = 5, 9, 13, . . . , e b
k
= 0 caso contrario. Ou seja,
os dgitos nas posies 4, 5, 8, 9, 12, 13, . . . sao 1, e os outros sao 0. Logo, a representaao
binaria de 1/10
1
10
= (0,0 0011 0011 0011 . . .)
2
A partir dessas contas, podemos ver alguns padres interessantes (em analogia com a
base 10). Umdeles que multiplicar e dividir por 2 tem, na base binaria, o mesmo efeito que
tinhamas multiplicaes/divises por 10 na base decimal. Ou seja, ao multiplicar por 2 um
nmero, a vrgula vai uma posiao para a direita na representaao binaria (ou acrescenta-se
um zero caso o nmero seja inteiro).
Outro padrao que fraes do tipo
a
2
n
1
, em que a < 2
n
1 um inteiro, sao otimas
geradoras de dzimas periodicas (binarias): 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 representaao de a tem no maximo n dgitos, e a srie geomtrica direita uma
dzima de perodo n, a representaao da nossa fraao sera uma dzima cujo perodo a
representaao de a (com zeros esquerda para completar os n dgitos se necessario).
O outro mtodo de achar a representaao decimal de uma fraao voc ja conhece:
o algoritmo da divisao. Para aplica-lo ao caso das fraes, basta escrever o denominador
e o numerador em binario e fazer o processo de divisao, lembrando que as regras do jogo
mudam um pouco por exemplo, voc so pode multiplicar o divisor por 0 ou 1. Nao vou
ensinar os detalhes aqui nao o proposito deste livro. Mas basicamente este o algoritmo
usado pelo computador para converter nmeros fracionarios para a representaao binaria;
assim que ele pode controlar quantos dgitos serao mantidos na representaao basta
parar o algoritmo na casa desejada.
Fizemos uma longa digressao sobre representaao binaria de nmeros. Para que isso
serviu: Agora poderemos entender melhor como funciona a representaao de ponto fu-
tuante. Como dissemos acima, na representaao de ponto futuante um nmero tem duas
partes: a mantissa e o expoente. Para a mantissa M devemos impor uma condiao de nor-
malizaao como no caso da notaao cientfca decimal: devemos ter 1 M < 2 para que
esquerda da vrgula haja ume apenas umdgito (nao-nulo). Assim, uma fraao 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 com um tamanho fxo (e
fnito) de dgitos. No caso da precisao simples do oat, esse nmero costuma ser de 24
Nmeros reais 37
dgitos. Assim, muitas fraes precisam ser arredondadas tanto as que tm representa-
es infnitas quanto as que tm representaes fnitas porm muito longas. Por exemplo,
a fraao 1/10 (representaao infnita) seria arredondada para
(1,100 1100 1100 1100 1100 1101)
2
2
4
= 0,100000001490116119384765625
Agora vamos chegar mais perto da representaao que realmente usada pelo compu-
tador. Um nmero de ponto futuante representado com a seguinte estrutura:
.. ..
sinal
31
63
..
expoente
30 23
62 52
..
mantissa
22 0
51 0
Figura 4.1: Esboo da estrutura de representaao de ponto futuante. Emcada parte foramindicados
os nmeros dos bits utilizados nas representaes de precisao simples e dupla, respectivamente.
Eu ainda nao havia falado do sinal: a representaao de ponto futuante usa um bit para
o sinal, assim como na representaao de inteiros. Mas, ao contrario desta, em ponto fu-
tuante nao se usa nada parecido com o complemento de 2; a nica diferena entre as
representaes de dois nmeros de mesma magnitude e sinais opostos o bit de sinal.
Oexpoente (relacionado a uma potncia de 2, nao de 10!) representado (quase) como
um nmero inteiro comum, e por isso ha uma limitaao nos valores que ele pode assumir.
Na precisao simples, sao reservados 8 bits para o expoente, o que permite at 236 valores
possveis nao sao exatamente de 128 a 127; na verdade, os dois extremos sao reservados
para situaes especiais; os expoentes representaveis sao de 127 at 126. Na precisao
dupla, usam-se 11 bits, e a gama de expoentes muito maior: de 1023 at 1022.
Sobre a mantissa nao resta muito a falar; os bits da mantissa sao armazenados sequen-
cialmente, como na representaao binaria que construmos acima. Em precisao simples,
sao reservados 23 bits para a mantissa. O leitor atento notara que eu falei em 24 dgitos
alguns paragrafos atras. De fato, a representaao binaria so reserva 23 dgitos. Mas, como
a mantissa esta sempre entre 1 e 2, esquerda da vrgula temos sempre um algarismo 1
sozinho; entao, convenciona-se que esse algarismo nao sera 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 precisao dupla, sao reservados 32 bits (que na verdade representam 33).
Na verdade, ha varios outros detalhes sobre a representaao de ponto futuante; poderia
gastar mais algumas paginas com essa descriao, mas nao vem ao caso. Meu objetivo era
dar uma idia geral do funcionamento dessa representaao.
At agora, so explorei a parte teorica dessa representaao. Precisamos conhecer tam-
bm as caractersticas praticas da representaao de ponto futuante. Por exemplo, em ter-
mos da representaao decimal, como se traduzem os limites de representaao do expoente
e da mantissa: Nao vou fazer contas aqui, vou apenas mostrar os resultados praticos. Des-
creverei primeiro que resultados sao esses:
Devido aos limites de expoentes, existe um nmero maximo e um nmero mnimo
que podem ser representados com uma dada precisao. O nmero maximo corres-
ponde ao maior expoente possvel coma maior mantissa possvel; o nmero mnimo,
analogamente, corresponde ao menor expoente possvel com a menor mantissa pos-
svel. Na tabela, sao apresentadas as faixas aproximadas.
38 Captulo 4. Mais sobre nmeros
Os mesmos limites se aplicam para os modulos dos nmeros negativos, ja que n-
meros negativos e positivos sao tratados de maneira simtrica na representaao de
ponto futuante.
Como varios nmeros precisamser arredondados para seremrepresentados, a repre-
sentaao tem um certo nmero de algarismos signifcativos de precisao quantas
casas decimais estao corretas na representaao de um nmero. Como os nmeros
nao sao representados em base 10, a quantidade de casas corretas pode variar de n-
mero para nmero; os valores apresentados na tabela seguir correspondem quan-
tidade mnima.
Tabela 4.3: Comparaao dos diferentes tipos de nmero de ponto futuante 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 32 bits 10
324
10
308
13 decimais
long double (80 bits) 13 bits 64 bits 10
4950
10
4932
19 decimais
Nota: A representaao de ponto futuante pode nao ser a mesma em todos os computadores. As
informaes aqui descritas estao de acordo com a representaao conhecida como IEEE 754, que
a mais usada nos computadores pessoais hoje em dia.
O tipo long double ainda nao tinha sido mencionado. Ele um tipo que, em geral, tem
precisao maior que o double (embora certos compiladores nao sigam essa norma), mas seu
tamanho nao tao padronizado como os outros dois tipos (precisao simples e dupla). Alm
de 80 bits, possvel encontrar tamanhos de 96 ou 128 bits.
De maneira similar ao double, necessario utilizar o codigo xLf em vez de apenas xf
quando queremos ler comscanf uma variavel longdouble. Nesse caso tambm necessario
utilizar esse mesmo codigo para o printf (em contraste com o double que continua usando
o codigo xf).
4.3.3 Problemas e limitaes
Como ja vimos, os tipos de ponto futuante nao conseguemrepresentar comexatidao todos
os nmeros fracionarios. Devido aos arredondamentos, acontecem alguns problemas que,
segundo a matematica que conhecemos, sao inesperados. Contas que, matematicamente,
dao 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 usuario que digite os coefcientes
reais de uma equaao quadratica
ax
2
+ bx + c = 0,
e analisaremos os trs tipos de soluao que podem ocorrer dependendo do valor de =
b
2
4ac (ainda nao vimos como calcular raiz quadrada, entao vamos apenas analisar os
casos):
Se > 0, a equaao tem duas razes reais distintas.
Nmeros reais 39
Se = 0, a equaao tem uma raiz real dupla.
Se < 0, a equaao nao tem razes reais.
O programa fcaria assim:
#inciude <sldio.h>
inl moin(}
{
fiool o b c deilo
prinlf("0 os coeficienles do equoo ox^Z + bx + c = 0. "}
sconf("xf xf xf" &o &b &c}
deilo = b*b - 4*o*c
prinlf("0eilo = xg`n" deilo}
if (deilo > 0}
prinlf("A equoo lem duos rozes reois dislinlos.`n"}
eise if (deilo == 0}
prinlf("A equoo lem umo roiz reoi dupio.`n"}
eise
prinlf("A equoo no lem rozes reois.`n"}
relurn 0
]
Esse programa, apesar de estar logicamente correto, apresenta umproblema. 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 varias dessas equaes,
descobrira um comportamento estranho: para muitos valores de , o valor de calculado
pelo programa nao igual a zero. Por exemplo, para valores nao inteiros e proximos de 1,
obtemos um da ordem de 10
7
.
Por que isso acontece: O problema sao os arredondamentos que ja vimos serem neces-
sarios para representar a maioria dos nmeros; por conta deles, nossa conta nao da exata-
mente zero no fnal.
Neste momento, considere isso como umalerta evite fazer comparaes de igualdade
com nmeros de ponto futuante; 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 futuante funcionambasicamente da mesma maneira
que operaes entre inteiros; voc pode at realizar operaes mistas entre nmeros in-
teiros e de ponto futuante o inteiro convertido automaticamente para ponto futuante
e a operaao realizada como se os dois nmeros fossem de ponto futuante. Em geral,
sempre que voc fornece um inteiro num lugar onde era esperado um nmero de ponto
futuante, o inteiro convertido para ponto futuante.
Por outro lado, a coerncia matematica da linguagem exige que o resultado de uma
operaao entre dois inteiros seja um inteiro. Agora lembremos que a divisao entre dois
60 Captulo 4. Mais sobre nmeros
nmeros inteiros nem sempre exata, e da surgem os nmeros racionais a divisao entre
dois nmeros inteiros deve ser, emgeral, umnmero racional. Como resolver esse confito:
Em C, a primeira regra (fechamento das operaes entre inteiros) a que prevalece:
quando dividimos dois inteiros, obtemos apenas o quociente da divisao inteira entre eles.
Para obter o resultado racional da divisao entre dois inteiros, precisamos converter umdeles
em ponto futuante.
Suponha que voc deseja imprimir a expansao decimal da fraao 1/7. Uma possvel
primeira tentativa seria a seguinte:
fiool x
x = 1/7
prinlf("1/7 = xf`n" x}
Para sua frustraao, voc obteria o nmero 0.000000 como resultado. Claro, se voc fez
uma divisao entre inteiros, o resultado foi a divisao inteira entre eles; ao guardar o resultado
numa variavel de ponto futuante, apenas o resultado convertido quem decide que tipo
de operaao sera realizada sao os operandos em si.
Para fazer o que queramos, pelo menos um dos operandos devera ser de ponto futu-
ante. 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 divisao entre dois nmeros que nao conhecemos a
priori: Devemos usar a
4.3.5 Converso explcita de tipos (casting)
Muitas converses de tipo sao feitas automaticamente em C, mas em alguns casos, como
na motivaao que antecede esta seao, preciso fazer uma conversao forada. Esse tipo
de converso explcita tambm conhecido pelo nome tcnico (em ingls) de casting. Ela
feita da seguinte maneira:
(novo_tipo) variavel_ou_valor
Por exemplo, para calcular a razao (fracionaria) entre dois inteiros fornecidos pelo
usuario, poderamos converter um deles para o tipo oat e realizar a divisao:
inl o b
fiool x
/* (...} ieiluro dos nmeros */
x = (fiool}o / b
prinlf("xf`n" x}
Note, porm, que o operador de conversao de tipos so atua sobre a variavel que vem
imediatamente depois dele por exemplo, se quisssemos converter uma expressao in-
teira, precisaramos de parnteses:
x = (fiool}(o / b} / c
Funes matemticas 61
Nesse caso, seria realizada a divisao inteira entre a e b, e depois seu quociente seria dividido
por c, numa divisao racional.
4.4 Funes matemticas
Emdiversas areas, as quatro operaes basicas nao cobremtodas as contas que precisamser
feitas em um programa. E muito comum, mesmo que nao estejamos lidando diretamente
com matematica, precisar das funes trigonomtricas, raiz quadrada, exponencial, entre
outras. Todas essas funes de uso comum constam no padrao da linguagem C e fazem
parte da biblioteca matematica padrao. Para usa-las em um programa, precisamos de mais
uma instruao no comeo do arquivo de codigo:
#inciude <molh.h>
Ao compilar com o GCC, tambm necessario incluir a biblioteca matematica na linha
de comando da compilaao; isso feito com a opao -im. (Na versao do MinGW para
Windows, devido diferena de organizaao do sistema, isso nao necessario.)
Uma lista completa das funes disponveis nesse arquivo pode ser facilmente encon-
trada nas referncias sobre a biblioteca padrao; na tabela 4.4 sao listadas apenas as princi-
pais. A maioria das funes aqui listadas recebe apenas um parametro, exatamente como
esperado; casos excepcionais serao indicados.
Tabela 4.4: Principais funes matematicas da biblioteca padrao
Funo Signicado
sin, cos, lon Funes trigonomtricas: seno, cosseno e tangente. Os angulos
sao sempre expressos em radianos.
osin, ocos, olon Funes trigonomtricas inversas. osin e olon devolvem um an-
gulo no intervalo
[
2
,
2
]
; ocos devolve umangulo no intervalo
[0, ].
sinh, cosh, lonh Funes hiperbolicas (seno, cosseno e tangente)
sqrl Raiz quadrada (square root)
exp Funao exponencial (e
x
)
iog Logaritmo natural, base e (ln)
iog10 Logaritmo na base 10
obs, fobs Modulo (valor absoluto) de um nmero. Use obs para inteiros e
fobs para nmeros de ponto futuante.
poW(x y} Potenciaao: x
y
(x e y podem ser nmeros de ponto futuante)
4.4.1 Clculo de sries
Varias das funes implementadas na biblioteca matematica podemser calculadas por meio
de expanses em srie (somatorias infnitas); por exemplo, a funao exponencial:
e
x
=
k=0
x
k
k!
= 1 + x +
x
2
2
+
x
3
3!
+
x
4
4!
+
62 Captulo 4. Mais sobre nmeros
E claro que, em um computador, impossvel calcular todos os termos dessa srie para
chegar ao resultado; apos um certo ponto, devido convergncia das sries, o valor de cada
termo muito pequeno, de modo que, frente precisao do computador, soma-los nao faz
mais diferena. Dessa maneira, devemos somar um nmero fnito Nde termos dessa srie.
Mas como escolher N: Seguem alguns dos critrios possveis:
Escolher umN fxo. Esse critrio nao muito bom, porque a precisao at o N-simo
termo costuma depender do argumento x; em varios casos, para valores grandes de
x necessario somar um nmero maior de parcelas, enquanto, para x bem pequeno,
poucas parcelas ja dao uma aproximaao muito boa.
Limitar a magnitude das parcelas a serem somadas interromper a soma quando o
modulo da parcela for menor que umdado (por exemplo, = 10
9
). Esse critrio
melhor que o anterior, mas nao leva emconta a magnitude do resultado; por exemplo,
se o valor da funao for da ordem de 10
9
, uma parcela de 10
9
realmente nao faz
diferena; se o valor da funao for da ordemde 10
6
, a mesma parcela de 10
9
ainda
signifcativa.
Podemos modifcar 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 nao 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 entao o critrio de limitaao na variaao relativa traduz-se matematicamente em
S
n+1
S
n
S
n
< ,
parando no passo n+1 se essa condiao for satisfeita. Esse critrio bastante usado
em diversos mtodos numricos.
Devido precisao do computador, em algum momento as parcelas fcarao tao pe-
quenas que absolutamente nao irao mais alterar a soma por exemplo, se estamos
trabalhando com 3 algarismos signifcativos, somar 0,0001 no nmero 100 nao 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, necessario tomar cuidado compossveis otimizaes do compilador
como voc somou uma parcela nao-nula, logicamente a soma deve ter-se alterado;
portanto, com certas opes de otimizaao ativadas, o compilador adianta a res-
posta da comparaao entre as duas somas e diz que elas sao diferentes, sem verifcar
se a variaao realizada estava realmente dentro da precisao do ponto futuante. O
resultado disso um lao infnito.
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 codigo organizado, pois evita que o codigo fque cheio
de nmeros anonimos difceis de entender. Alm disso, quando utilizamos uma cons-
tante pelo nome, ganhamos uma grande fexibilidade: se precisamos alterar o valor dela,
O tipo char 63
podemos alterar simplesmente a sua defniao, e a alteraao refetir-se-a emtodos os lugares
onde a constante foi utilizada.
Para defnir constantes, utilizamos o comando #define, da seguinte maneira:
#dene CONSTANTE valor
As restries sobre os nomes das constantes sao as mesmas que para nomes de variaveis e
funes. Para utilizar essas constantes,
Um exemplo de aplicaao disso: suponha que voc criou um programa que mostra o
cardapio de uma pizzaria, recebe um pedido e calcula o valor total. Agora a pizzaria quer
que voc refaa o programa pois os preos foramreajustados. Se voc tiver escrito os preos
diretamente no meio do codigo do programa, voc tera bastante trabalho procurando os
preos e modifcando em diversos lugares. No entanto, se voc defnir constantes como
PREC0MARC|ERTTA e PREC0P0RTuCuESA e usa-las no codigo, voc so precisara alterar a
defniao das constantes.
#inciude <sldio.h>
#define PREC0MuSSARELA 15.00
#define PREC0MARC|ERTTA 16.00
#define PREC0P0RTuCuESA 1.50
void imprimecordopio(}
{
prinlf (
"Mussoreio x5.Zf`n"
"Morgherilo x5.Zf`n"
"Porlugueso x5.Zf`n"
PREC0MuSSARELA PREC0MARC|ERTTA PREC0P0RTuCuESA}
]
/* 0eixorei como exerccio poro voc pensor o porle de regislror um
* pedido. Wo verdode por enquonlo voc pode se preocupor openos em
* coicuior o preo loloi.
* Sugeslo. use um lerminodor poro poder sober quondo o pedido ocobo.
*/
4.5 Otipo char
Voc ja conheceu trs dos quatro tipos fundamentais da linguagem C. Falta apenas um, o
tipo char que ja mencionamos, mas nao tivemos a oportunidade de usar. Como o nome
sugere, ele designado para guardar caracteres (como o Y ^ { e # x). 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 sao codifcados como nmeros de acordo com uma tabela
de correspondncia por exemplo, a letra A maiscula armazenada como 63, o cifrao S
como 36, etc. Esses exemplos sao os codigos da conhecida tabela ASCII, que a base do
padrao atual para a codifcaao de caracteres.
64 Captulo 4. Mais sobre nmeros
Uma variavel do tipo char pode conter um caractere (apenas um!), e caracteres sao
representados entre aspas simples (nao confunda comas aspas duplas como as que voc usa
nas funes printf e scanf). Alguns exemplos: o, Z, e. Um exemplo de variavel do
tipo chor seria:
chor opcoo
opcoo = b
Ha alguns caracteres que nao sao representados literalmente, mas por meio de codigos
especiais. Um deles ja lhe foi apresentado, a saber, o caractere de quebra de linha, repre-
sentado pela sequncia `n. Alguns outros exemplos sao apresentados na tabela 4.3, junto
com os codigos numricos correspondentes na tabela ASCII.
Tabela 4.5: Algumas das sequncias utilizadas para representar caracteres especiais.
Sequncia ASCII Signicado
`l 9 Tabulaao. Avana o cursor para posies pr-defnidas ao longo
da linha; usualmente, essas posies sao defnidas de 8 em 8 ca-
racteres a partir da primeira posiao da linha.
`n 10 Quebra de linha. Esse caractere comumente conhecido pela si-
gla 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 observaao adiante.)
`" 34 Aspa dupla.
` 39 Aspa simples.
`` 92 Barra invertida.
`nnn Permite especifcar qualquer caractere pelo seu codigo 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 nao foram indicados aqui) sao uma herana
da poca das maquinas de escrever. Para comear a escrever texto na linha seguinte, eram
necessarias duas aes: retroceder o carro de impressao para o comeo da margem (CR)
e alimentar mais uma linha de papel (LF). Em sistemas Windows, o fnal 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 representaao alternativa: as duas aspas
porque podemos querer representa-las como caracteres sem que sejam interpretadas como
o fnal da sequncia de caracteres; a barra invertida porque ela propria usada em outras
sequncias de caracteres especiais.
4.5.1 Entrada e sada
Para ler e imprimir variaveis do tipo char, voc pode usar o codigo de formato xc nas fun-
es printf e scanf. Por exemplo:
chor opcoo
O tipo char 63
sconf("xc" &opcoo}
prinlf("voc escoiheu o opo (xc}!`n" opcoo}
inl opcoo
opcoo = gelchor(}
pulchor(o}
E importante notar que, tanto com a funao scanf quanto com a getchar, os caracteres
digitados nao sao recebidos pelo programa em tempo real. Cada vez que voc digita um
caractere, ele armazenado temporariamente numa regiao da memoria chamada buer
de teclado, e so apos o fnal 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 variavel guardada em uma certa posiao da memoria. Essas po-
sies de memoria sao 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 variavel que guarda uma
referncia a outra variavel seu valor o endereo de uma outra variavel. Se o valor de
um ponteiro p o endereo de uma variavel X, entao dizemos que p aponta para X. Veja
uma ilustraao disso na fgura 3.1. Se um ponteiro aponta para uma variavel, voc pode
acessar essa variavel (ler ou alterar seu valor) atravs do ponteiro logo veremos como.
Figura 5.1: Esquema do funcionamentode umponteiro. Oponteirop contmo endereo da variavel
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
programa, podemos passar apenas umponteiro para esses dados emvez de fazer uma
copia dos dados e transmitir a copia. Isso economiza tempo o processo de duplicar
os dados gasta tempo de processamento e, obviamente, espao na memoria.
Uma funao em C so pode devolver um valor com a instruao return. No entanto,
se uma funao recebe como parametros ponteiros para outras variaveis, voc podera
gravar valores nessas variaveis, e com isso uma funao pode gerar varios valores de
sada.
67
68 Captulo 5. Ponteiros e vetores
O conceito de ponteiros pode ser estendido para funes possvel passar um
ponteiro para uma funo como parametro. Por exemplo, podemos criar uma funao
chamada ochoroiz com um algoritmo numrico que acha razes de uma funao
matematica f; a funao f poderia ser passada (na forma de ponteiro) como parametro
da funao ochoroiz.
5.1.1 Declarao de ponteiros
Em C, para declarar uma variavel que funciona como ponteiro, colocamos um asterisco (*)
antes do seu nome. Umponteiro so pode apontar para umtipo de variavel, ja que a maneira
de armazenar o valor de uma variavel na memoria depende do seu tipo. Por exemplo, um
ponteiro que aponta para uma variavel inteira nao pode ser usado para apontar para uma
variavel de ponto futuante. Assim, um ponteiro tambm precisa de um tipo, que deve ser
igual ao tipo de variavel para a qual ele ira apontar.
Por exemplo, se queremos criar umponteiro p que ira apontar para uma variavel inteira,
declaramo-no da seguinte maneira:
inl *p
Essa instruao apenas declara um ponteiro, sem aponta-lo para nenhuma variavel.
Se quisermos declarar varios ponteiros com uma nica instruao, devemos colocar o
asterisco em cada um deles. Se voc escrever o asterisco apenas no primeiro nome, apenas
a primeira variavel sera um ponteiro!
inl *p1 *pZ *p3 /* ok os lrs so ponleiros */
doubie *p4 p5 p6 /* probiemo! so p4 sero um ponleiro */
Para fazer um ponteiro apontar para uma variavel, devemos atribuir-lhe como valor o
endereo de outra variavel, e nao um nmero comum. Para isso, usamos o operador &
(E comercial), que fornece o endereo de uma variavel aqui ele sera conhecido como o
operador endereo-de. Se temos uma variavel var, seu endereo representado por &vor.
Por exemplo:
inl n
inl *p
p = &n
Aqui criamos uma variavel inteira chamada n, e em seguida criamos um ponteiro p, que
apontado para a variavel n.
5.1.2 Acesso indireto por ponteiros
Para acessar a variavel que apontada por um ponteiro, usamos o operador * (o mesmo
asterisco usado na declaraao), chamado operador de indireo ou operador de desreferen-
ciao. Esse operador faz a volta do processo que leva da variavel ao ponteiro (a refe-
renciao da variavel); por isso o chamamos de operador de desreferenciaao. O nome
indireao usado simplesmente porque isso um acesso indireto variavel.
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 69
Nomenclaturas parte, se p um ponteiro, podemos acessar a variavel para a qual ele
aponta com*p. Essa expressao pode ser usada tanto para ler o contedo da variavel quando
para altera-lo.
var p
&var
*p