Você está na página 1de 26

Assembly NumaBoa - Capítulo 1

REPRESENTAÇÃO DE DADOS
A notação binária e a notação hexadecimal são, provavelmente, os maiores obstáculos para os iniciantes. Apesar de
parecerem estranhas no primeiro contato, logo percebemos que suas vantagens superam as desvantagens quando
trabalhamos com a linguagem Assembly. Seu uso simplifica a álgebra booleana, o desenho lógico, a representação
dos números com sinal, códigos de caracteres e dados compactados. Além disso, preciso dizer que não há como se
tornar um expert no assunto se não dominarmos estas notações numéricas.

Taí um bom começo. Neste primeiro capítulo vamos analisar alguns conceitos importantes, incluindo os sistemas de
notação binária e hexadecimal, a organização de dados binários (bits, nibbles, bytes, words e double words),
sistemas de numeração com e sem sinal, operações aritméticas, lógicas, shift e rotate com valores binários, campos
de bits e dados compactados, além do conjunto de caracteres ASCII.

Se você já estiver familiarizado com estes conceitos, não deixe de dar uma passada de olhos no texto. Se tudo for
novidade, estude com capricho. Tudo neste capítulo é importante, pois uma boa base é essencial para poder
progredir.

Revisão do Sistema Decimal

Em se tratando do sistema decimal, é interessante notar que a maioria das pessoas não se dá conta do que realmente
ele representa. Quando falamos do número 123, imaginamos um certo número de itens que este número representa e
esquecemos da sua origem.

O sinal ^ significa "elevado a". Neste caso, 123 representa

(1 x 10^2) + (2 x 10 ^1) + (3 x 10 ^0)


ou seja,
100 + 20 + 3 = 123

As posições dos dígitos são numeradas da direita para a esquerda de 0 (zero) até a posição do último dígito da
esquerda e são a potência de 10 que multiplica o dígito. Se o valor possuir casas decimais, como por exemplo
123.45, cada casa após a vírgula é numerada de -1 até a posição do último dígito à direita e são a potência de 10 que
multiplica o dígito. Portanto

(1 x 10^2) + (2 x 10^1) + (3 x 10^0) + (4 x 10^-1) + (5 x 10^-2)


ou seja,
100 + 20 + 3 + 0.4 + 0.05 = 123.45

O Sistema Binário

Por enquanto os sistemas dos computadores operam usando a lógica binária. O computador representa valores
usando dois níveis de voltagem, geralmente 0 Volts e +5 Volts. Com estes dois níveis podemos representar dois
valores diferentes, que poderiam ser quaisquer valores, mas convencionou-se que representassem 0 (zero) e 1 (um).
Estes dois valores, por coincidência, correspondem aos dois dígitos usados pelo sistema de numeração binário.
Unindo o útil ao agradável, basta transportar nossos conhecimentos sobre o sistema decimal para o sistema binário.

O sistema binário funciona exatamente como o sistema decimal (só nossa cabeça é que insiste em dar nó). Se o
sistema decimal possui 10 dígitos (de 0 a 9), o sistema binário possui 2 (de 0 a 1). Se fazemos potência de 10 para
calcular quantidades no sistema decimal, faremos potência de 2 para calcular quantidades no sistema binário. Por
exemplo, o valor binário 11001010 representa o valor decimal 202.
1
(1x2^7) + (1x2^6) + (0x2^5) + (0x2^4) + (1x2^3) + (0x2^2) + (1x2^1) + (0x2^0)
ou seja,
128 + 64 + 0 + 0 + 8 + 0 + 2 + 0 = 202

Transformar decimal em binário é um pouquinho mais trabalhoso. Já que a nova base deve ser 2, basta ir dividindo
o decimal por 2 e ir colecionando os restos. Tomemos como exemplo o decimal 458:

458 / 2 = 229 resta 0


229 / 2 = 114 resta 1
114 / 2 = 57 resta 0
57 / 2 = 28 resta 1
28 / 2 = 14 resta 0
14 / 2 = 7 resta 0
7 / 2 = 3 resta 1
3 / 2 = 1 resta 1
1 / 2 = 0 resta 1

Agora basta ler os restos de trás para frente para obter a notação binária do decimal 458, ou seja, 111001010.
Apesar da notação binária ter pouca importância em linguagens de alto nível, ela aparece com frequência em
programas escritos em Assembly.

Formatos binários

Assim como nos números decimais, os zeros colocados à esquerda de dígitos binários não são significantes.
Podemos colocar infinitos zeros à esquerda de um número binário e, nem assim, seu valor se modifica. Veja o
número binário 101, que corresponde a 5 decimal:

101 = 0000 0101 = ...00000000000000000000101

Como ficou convencionado de que 1 byte possui oito bits, e um bit (derivado de binary digit) representa um dígito
binário, vamos adotar a convenção de grafá-los sempre em múltiplos de quatro casas. Por exemplo, o decimal 5
poderá ser grafado como 0101, 00000101, 000000000101 ou mesmo 0000000000000101. No sistema decimal
costumamos separar os dígitos em grupos de três: 1.748.345 é mais legível que 1748345. No sistema binário
agruparemos os dígitos em grupos de quatro, também para melhorar a legibilidade. Por exemplo,
0000000000000101 será grafado 0000 0000 0000 0101.

Os dígitos dos números binários são numerados da direita para a esquerda iniciando-se com 0 (zero). Na verdade,
esta numeração indica a potência de 2 do dígito em questão. Veja abaixo:

dígitos binários 0 1 0 1 (corresponde a 5 decimal)


numeração 3 2 1 0
potência de 2 2^3 2^2 2^1 2^0

O bit na posição 0 (zero) é denominado de bit de ordem baixa (low order) ou menos significativo. O bit na
extremidade esquerda é denominado de bit de ordem alta (high order) ou mais significativo.

Organização de Dados

Na matemática pura, qualquer valor pode ter um número infinito de dígitos (lembrando que os zeros colocados à
esquerda não alteram o valor do número). Com os computadores a coisa é um pouco diferente, pois trabalham com
um número específico de bits. Os grupos de dígitos binários mais comumente utilizados pelos computadores são:
bits únicos, grupos de 4 bits - chamados de nibble, grupos de 8 - chamados de byte, grupos de 16 - chamados de
word (word = palavra), etc. A razão da existência destes grupos é funcional, característica dos chips 80x86.
2
O bit

A menor "unidade" de dados num computador binário é um bit. Como um único bit consegue representar
unicamente dois valores diferentes (tipicamente zero ou um), fica a impressão de que um bit só consegue
representar um número muito limitado de itens. Não é bem assim.

O que precisa ficar claro é a dualidade do bit. Esta dualidade pode se referir a itens de um mesmo tipo ou a itens de
natureza completamente diferente. Um bit pode representar 0 ou 1, verdadeiro ou falso, ligado ou desligado,
masculino ou feminino, certo ou errado. Um bit também pode representar quaisquer dois valores (como 589 ou
1325) ou duas cores (como azul ou vermelho). Nada impede de que um bit represente dois itens de natureza distinta,
como 589 ou vermelho. Podemos representar qualquer par de itens com um bit, mas apenas um único par de itens.

A coisa pode ficar ainda mais complexa quando bits diferentes representarem itens diferentes. Por exemplo, um bit
pode representar os valores 0 ou 1, enquanto outro bit adjacente pode representar os valores falso ou verdadeiro.
Olhando apenas para os bits, não é possível reconhecer a natureza do que estejam representando. Isto mostra a idéia
que está por trás das estruturas de dados num computador: dados são o que você definiu. Se você usar um bit para
representar um valor booleano (falso/verdadeiro), então este bit, de acordo com a definição que você deu, representa
falso ou verdadeiro. Para que o bit tenha significado, é preciso manter a consistência: se você estiver usando um bit
para representar falso ou verdadeiro, este bit, em todos os pontos do seu programa, deve apenas conter a informação
falso ou verdadeiro; não pode ser usado para representar cores, valores, ou qualquer outro tipo de item.

Como a maioria dos itens que precisam ser representados possuem mais do que dois valores, bits únicos não são o
tipo de dado mais usado. Conjuntos de bits são os tipos mais utilizados em programas e valem uma análise mais
detalhada.

O nibble

Um nibble é um conjunto de quatro bits. Não seria um tipo de dado muito interessante não fosse a existência de dois
itens especiais: números BCD (binary coded decimal) e números hexadecimais. Um dígito BCD ou um dígito
hexadecimal precisa exatamente de quatro bits para ser representado. Com um nibble podemos representar até 16
valores distintos. No caso dos números hexadecimais, cada um dos valores 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E e
F é representado por quatro bits. Quaisquer 16 valores distintos podem ser representados por um nibble, mas os
mais importantes e conhecidos são os dígitos BCD e hexadecimais.

O byte

Sem dúvida alguma, a estutura de dados mais importante usada pelo microprocessador 80x86 é o byte. Um byte é
um conjunto de oito bits, o menor item de dado endereçável no 80x86. Isto significa que o menor item que pode ser
acessado individualmente por um programa 80x86 é um valor de oito bits. Para acessar qualquer coisa menor, é
preciso ler o byte que contenha os dados e usar uma máscara para filtrar os bits desejados. Os bits de um byte
também são numerados da direita para a esquerda, de 0 a 7.

O bit 0 é o bit de ordem baixa (O.B.) ou menos significativo e o bit 7 é o bit de ordem alta (O.A.) ou mais
significativo. Os outros bits são referenciados pelos seus números. Observe que um byte possui dois nibbles.

3
O nibble com os bits de 0 a 3 é o nibble de ordem baixa (O.B.) ou menos significativo e o nibble com os bits de 4 a
7 é o nibble de ordem alta (O.A.) ou mais significativo. Como o byte possui dois nibbles e cada nibble corresponde
a um dígito hexadecimal, valores byte são expressos através de dois dígitos hexadecimais.

Como um byte possui 8 bits, ele pode representar 2^8 = 256 valores diferentes. Geralmente um byte é utilizado para
representar valores numéricos positivos de 0 a 255, valores numéricos com sinal de -128 a 127, os códigos dos
caracteres ASCII e outros tipos especiais de dados não necessitem de mais do que 256 valores diferentes. Muitos
tipos de dados possuem menos do que 256 itens, de modo que oito bits são suficientes para representá-los.

Uma vez que o 80x86 é uma máquina de bytes endereçáveis, é mais eficiente manipular um byte completo do que
um bit individual ou um nibble. Por esta razão, a maioria dos programadores usam o byte completo para representar
tipos de dados, mesmo quando possuam menos do que 256 itens. Por exemplo, é comum representar os valores
booleanos falso e verdadeiro com 0000 0000 e 0000 0001.

O uso mais importante do byte é, provavelmente, para representar um código de caracter. Todos os
caracteres digitados no teclado, mostrados na tela ou impressos numa impressora, possuem um valor
numérico. Para padronizar estes valores, criou-se o conjunto de caracteres ASCII. O conjunto ASCII
básico possui 128 códigos. Os 128 restantes são utilizados com valores para caracteres adicionais como
caracteres europeus, símbolos gráficos, letras gregas e símbolos matemáticos. Veja a tabela ASCII de
caracteres/códigos.

O word

O word (palavra) é um grupo de 16 bits, numerados da direita para a esquerda de 0 a 15.

O bit 0 é o menos significativo e o bit 15 o mais significativo. Os restantes são referenciados pelos seus números.
Observe que o word é composto por dois bytes. O byte com os bits de 0 a 7 é o byte menos significativo ou de
ordem baixa (O.B.) e o byte com os bits de 8 a 15 é o byte mais significativo ou de ordem alta (O.A.)

É claro que um word também pode ser dividido em quatro nibbles. O nibble menos significativo no word, de O.B.,
é o nibble 0 e o nibble mais significativo no word, de O.A., é o nibble 3.

4
Com 16 bits é possível obter 2^16 = 65.536 valores diferentes. Estes podem ser valores numéricos positivos de 0 a
65.535, numéricos com sinal de -32.768 a 32.767 ou qualquer outro tipo de dado que possua até 65.536 valores.
Words são usados principalmente para três tipos de dados: valores inteiros, deslocamentos (offsets) e valores de
segmento.

Words podem representar valores inteiros de 0 a 65.535 ou de -32.768 a 32.767. Valores numéricos sem sinal são
representados pelo valor binário que corresponde aos bits no word. Valores numéricos com sinal usam a forma de
complemento de dois (adiante entraremos em detalhe). Valores de segmento, que sempre têm comprimento de 16
bits, constituem o endereço de memória de parágrafos do código, de dados, do segmento extra ou do segmento da
pilha.

O double word

O double word (palavra dupla) é o que o nome indica: um par de words. Portanto, um double word é um conjunto
de 32 bits.

Naturalmente, um double word pode ser quebrado em 2 words, 4 bytes ou 8 nibbles.

Double words podem representar todo tipo de coisa. Em primeiro lugar estão os endereços segmentados. Outro item
comumente representado por um double word são os valores inteiros de 32 bits, que podem ir de 0 a 4.294.967.295,
ou números com sinal, que podem ir de -2.147.483.648 a 2.147.483.647. Valores de ponto flutuante de 32 bits
também cabem num double word. Na maioria das vezes, os double words são usados para armazenarem endereços
segmentados.

Comentários

Antes de começar os exercícios propostos, leia o texto adicional Asm - Sistemas de Notação, que é uma outra
abordagem deste assunto. Existe um aplicativo escrito em Object Pascal (Delphi) pelo Randall Hyde que traduzi
para o Português e recompilei. É um conversor hexa/decimal/binário. Você pode fazer o download do executável
com código fonte aqui na Aldeia.

Treine exaustivamente a notação decimal e binária, faça conversões de binário para decimal e de decimal para
binário. Além disso, construa uma tabela de valores decimais e suas respectivas notações binárias. Construa nibbles,
bytes e words com os valores binários encontrados. Numere os bits, os nibbles e os bytes e localize os mais e os
menos significativos. Em resumo, familiarize-se o máximo que puder com os bits e bytes, pois eles serão a matéria
prima da nossa programação!

5
REPRESENTAÇÃO DE DADOS II
O Sistema de Numeração Hexadecimal

O grande problema do sistema binário é sua verbosidade. Para representar o valor decimal 202, de apenas três casas,
precisamos de oito casas binárias. É óbvio que com um conjunto de dez dígitos possíveis, o sistema decimal pode
representar números de uma forma muito mais compacta do que o sistema binário, que possui um conjunto de
apenas dois dígitos. Valores grandes precisam de uma infinidade de casas binárias, tornando o número praticamente
inutilizável para os mortais comuns. Além disso, as conversões entre decimal e binário são um tanto trabalhosas.
Para mal dos pecados, o computador só "pensa" em binário. Resolveu-se então partir para uma solução radical: criar
um sistema cuja base fosse a mesma do tipo mais usado nos computadores. Como já vimos no módulo anterior, os
tipos mais utilizados são o byte e o word. Um byte possui oito bits - então foi criado um sistema octal. Um word
possui 16 bits - então foi criado o sistema hexadecimal.

Os computadores evoluíram rapidamente para sistemas baseados em 16 bits e o sistema hexadecimal ganhou força.
Ele oferece exatamente o que precisamos: gera representações numéricas compactas e as conversões entre
hexadecimal e binário são simples. Como a base de um número hexadecimal é 16, cada casa representa uma
potência de 16. Vamos tomar como exemplo o número hexadecimal 1234 (lembre-se de que a potência é indicada
por ^):

dígitos hexadecimais 1 2 3 4
numeração 3 2 1 0
potência de 16 16^3 16^2 16^1 16^0

ou seja

(1 x 16^3) + (2 x 16^2) + (3 x 16^1) + (4 x 16^0) =


4096 + 512 + 48 + 4 = 4660 decimal

Cada dígito hexadecimal pode representar um dos dezesseis valores entre 0 e 15. Como só existem dez dígitos
decimais, foi preciso inventar seis dígitos adicionais. Optou-se pelas letras de A a F. Alguns exemplos de números
hexadecimais seriam 1234, CADA, BEEF, 0FAB, FADA, FEFE, FAFA, etc. Como vamos nos referir com
frequência a números em várias notações, é bom por ordem na casa desde já. Nos textos serão usadas as seguintes
convenções:

• Todos os valores numéricos, independente da sua base, começam com um dígito decimal.
• Todos os valores hexadecimais terminam com a letra "h".
• Todos os valores binários terminam com a letra "b".
• Todos os valores decimais terminam com o sufixo "d".

São exemplos válidos: 1234h, 0CADAh, 0FADAh, 4660d, 101b. Dá para notar que os números
hexadecimais são compactos e de fácil leitura. Além disso, as conversões são fáceis. Veja a seguinte
tabela que fornece toda a informação necessária para fazer a conversão de hexa para binário e vice versa:

6
Hexadecimal Binário
0 0000
1 0001
2 0010
3 0011
4 0100
5 0101
6 0110
7 0111
8 1000
9 1001
A 1010
B 1011
C 1100
D 1101
E 1110
F 1111

Para converter um número hexa num número binário, substitui-se simplesmente cada um dos dígitos hexa pelos
quatro bits do dígito binário correspondente. Por exemplo, para converter 0ABCDh num valor binário:

hexadecimal A B C D
binário 1010 1011 1100 1101

Para converter um número binário em hexa, o processo é tão fácil quanto o anterior. A primeira providência é
transformar o número de dígitos do valor binário num múltiplo de quatro. Depois é só substituir. Veja o exemplo
abaixo com o binário 1011001010:

binário 1011001010
grupos de 4 dígitos 0010 1100 1010
hexadecimal 2 C A

Operações aritméticas com números Binários e Hexadecimais

À primeira vista, as operações de soma, subtração, multiplicação e divisão (além de outras) parecem muito fáceis de
serem realizadas com números hexadecimais e binários. Mas cuidado! Seu cérebro insiste em trabalhar com a base
10, "vício" adquirido na infância nos primeiros anos de escola. Veja alguns exemplos:

9h + 1h = ?

Se você respondeu 10h, seu cérebro passou-lhe uma rasteira. A resposta correta é 0Ah, que corresponde ao 10
decimal. Mais um exemplo:

10h - 1h = ?

Novamente, se você respondeu 9h, deu outra derrapada. O correto é 0Fh, uma vez que, no sistema deciaml, 16 - 1 =
15.

Com o sistema binário a coisa fica um pouco pior, pois a possibilidade de erro já começa ao se escrever sequências
muito longas de 0s e 1s. Moral da história: ou se transforma os valores em decimal, efetua-se a operação e volta-se a
transformar o resultado para o sistema original ou... usa-se uma calculadora que faça operações com números
binários e hexa. A própria calculadora do Windows, quando no modo científico, é uma boa ferramenta.
7
Operações lógicas com bits

As principais operações lógicas são AND, OR, XOR e NOT. É imprescindível dominá-las perfeitamente. Costumo
usar uns métodos menumônicos para trazê-las de volta à memória. Comecemos com a operação AND.

AND

A tradução de AND é E. Meu menumônico é "se eu estiver cansada E tiver um lugar para deitar, então eu durmo".
Somente se as duas condições forem verdadeiras, o resultado é verdadeiro. Se eu estiver cansada (primeira condição
é verdadeira) mas não tiver uma rede ou uma cama para deitar (segunda condição é falsa), então não vou conseguir
dormir (resultado falso). Se eu não estiver cansada (primeira condição falsa), mas tenho uma rede para deitar
(segunda condição verdadeira), nem por isso vou dormir (resultado falso).

A operação lógica AND é uma operação diádica (aceita exatamente dois operandos) e seus operandos são dois bits.
AND pode ser resumida na seguinte tabela, onde 0 representa falso e 1 representa verdadeiro:

AND 0 1
0 0 0
1 0 1

Outra maneira de guardar a operação lógica AND é compará-la com a multiplicação - multiplique os operandos que
o resultado também é correto. Em palavras, "na operação lógica AND, somente se os dois operandos forem 1 o
resultado é 1; do contrário, o resultado é 0".

Um fato importante na operação lógica AND é que ela pode ser usada para forçar um resultado zero. Se um dos
operandos for 0, o resultado é sempre zero, não importando o valor do outro operando. Na tabela acima, por
exemplo, a linha que do operando 0, só possui 0s; e a coluna do operando 0, também só possui 0s. Por outro lado, se
um dos operandos for 1, o resultado é o outro operando. Veremos mais a respeito logo adiante.

OR

A operação lógica OR (cuja tradução é OU) também é uma operação diádica. Meu mneumônico é "se alguém me
xingar OU se fizer uma rosquinha, então fico brava". Daí fica fácil fazer a tabela da lógica OR:

OR 0 1
0 0 1
1 1 1

Em outras palavras, "se um dos operandos for verdadeiro, o resultado é verdadeiro; do contrário, o resultado é
falso". Se um dos operandos for 1, o resultado sempre será 1, não importando o valor do outro operando. Por outro
lado, se um dos operandos for 0, o resultado será igual ao outro operando. Estes "efeitos colaterais" da operação
lógica OR também são muito úteis e também serão melhor analisados logo adiante.

XOR

A tradução de XOR (exclusive OR) é OU exclusivo (ou excludente). Esta operação lógica, como as outras, também
é diádica. A minha forma de lembrar é "ir ao supermercado XOR ir ao cinema, preciso me decidir". Como não

8
posso estar nos dois lugares ao mesmo tempo (um exclui o outro), então a tabela da lógica XOR passa a ser a
seguinte:

XOR 0 1
0 0 1
1 1 0

Se não for ao supermercado (0) e não for ao cinema (0), então não decidi o que fazer (0). Se for ao supermercado
(1) e não for ao cinema (0), então me decidi (1). Se não for ao supermercado (0) e for ao cinema (1), então também
me decidi (1). Se for ao supermercado (1) e for ao cinema (1), não decidi nada (0) porque não posso ir aos dois
lugares ao mesmo tempo. Em outras palavras, "se um dos operandos for 1, então o resultado é 1; caso contrário, o
resultado é 0".

Se os operandos forem iguais, o resultado é 1. Se os operando forem diferentes, o resultado é zero. Esta
característica permite inverter os valores numa seqüência de bits e é uma mão na roda.

NOT

Esta é a operação lógica mais fácil, a da negação. NOT significa NÃO e, ao contrário das outras operações, aceita
apenas um operando (é monádica). Veja a tabela abaixo:

NOT 0 1
NOT 1 0

Operações lógicas com Números Binários e Strings de Bits

Como foi visto acima, as funções lógicas funcionam apenas com operandos de bit único. Uma vez que o 80x86 usa
grupos de oito, dezesseis ou trinta e dois bits, é preciso ampliar a definição destas funções para poder lidar com
mais de dois bits. As funções lógicas do 80x86 operam na base do bit a bit, ou seja, tratam os bits da posição 0,
depois os bits da posição 1 e assim sucessivamente. É como se fosse uma cadeia de operações. Por exemplo, se
quisermos realizar uma operação AND com os números binários 1011 0101 e 1110 1110, faríamos a operação
coluna a coluna:

1011 0101
AND 1110 1110
-----------
1010 0100

Como resultado desta operação, "ligamos" os bits onde ambos são 1. Os bits restantes foram zerados. Se quisermos
garantir que os bits de 4 a 7 do primeiro operando sejam zerados e que os bits 0 a 3 fiquem inalterados, basta fazer
um AND com 0000 1111. Observe:

1011 0101
AND 0000 1111
-----------
0000 0101

Se quisermos inverter o quinto bit, basta fazer um XOR com 0010 0000. O bit (ou os bits) que quisermos inverter,
mandamos ligado. Os bits zerados não alteram os bits do primeiro operando. Assim, se quisermos inverter os bits 0
a 3, basta fazer um XOR com 0000 1111.

1011 0101
XOR 0000 1111
-----------
9
1011 1010

E o que acontece quando usamos um OR com 0000 1111? Os bits 0 não alteram os bits do primeiro operando e os
bits 1 forçam os bits para 1. É um método excelente para ligar bits na (ou nas) posições desejadas.

1011 0101
OR 0000 1111
-----------
1011 1111

Este método é conhecido como máscara. Através de uma máscara de AND é possível zerar bits. Com uma máscara
XOR é possível inverter bits e, através de uma máscara OR é possível ligar bits. Basta conhecer as funções e saber
lidar com os bits. Quando temos números hexadecimais, o melhor é transformá-los em binário e depois aplicar as
funções lógicas... é lógico ;))))

Números com e sem sinal

Até agora tratamos os números binários como valores sem sinal. Mas como se faz para representar números
negativos no sistema binário? É aí que entra o sistema de numeração do complemento de dois. Vamos lá.

Os números, no computador, não podem ser infinitos pelo simples fato de que a quantidade de bits disponível para
expressá-los é restrita (8, 16, 32, ou qualquer quantidade que nunca é muito grande). Com um número fixo de bits, o
valor máximo também é fixo. Por exemplo, com 8 bits podemos obter no máximo o valor 256. Se quisermos
expressar números negativos, teremos que dividir estas 256 possibilidades, metade para os positivos e metade para
os negativos. Isto diminui o valor máximo, porém aumenta o valor mínimo. Se a divisão for bem feita, podemos
obter -128 a 0 e 0 a 127. O mesmo raciocínio pode ser usado para 16 bits, 32bits, etc. Como regra geral, com n bits
podemos representar valores com sinal entre

-2^(n-1) e 2^(n-1)-1

Muito bem, já sabemos que podemos dividir o espaço dos valores numéricos oferecido pelos bits, mas ainda não
sabemos como representar os valores negativos usando os bits. O microprocessador 80x86 usa a notação de
complemento de dois. Neste sistema, o bit mais significativo é que sinaliza se o número é positivo ou negativo: se
for 0, o número é positivo; se for 1, o número é negativo. Veja os exemplos:

8000h é negativo => 1000 0000 0000 0000


100h é positivo => 0000 0001 0000 0000
7FFFh é positivo => 0111 1111 1111 1111
FFFFh é negativo => 1111 1111 1111 1111

Se o bit O.A. for zero, então o número é positivo e é armazenado como um valor binário padrão. Se o bit O.A. for
um, então o número é negativo e é armazenado na forma de complemento de dois. Para converter um número
positivo para negativo use o seguinte algoritmo:

1. Inverta todos os bits do número com uma operação lógica NOT.


2. Adicione 1 ao resultado.

No seguinte exemplo faremos a conversão de +5 para -5 usando o complemento de dois usando apenas 8 bits:

decimal 5 binário 0000 0101 (05h)


inverter bits 1111 1010 (0FAh)
somar 1 1111 1011 (0FBh)

Se repetirmos a operação com o valor encontrado para -5, voltamos a obter o valor original:
10
decimal -5 binário 1111 1011 (0FBh)
inverter bits 0000 0100 (04h)
somar 1 0000 0101 (05h)

Agora observe o que acontece com o hexadecimal 8000h, o menor número negativo com sinal (-32.768):

hexa 8000h
binário 1000 0000 0000 0000 (8000h)
inverter bits 0111 1111 1111 1111 (7FFFh)
somar 1 1000 0000 0000 0000 (8000h) ???

Invertendo 8000h obtemos 7FFFh e, somando 1, voltamos para 8000h. Tem alguma coisa errada pois -(-32768) não
pode ser igual a -32768! O que ocorre é que, com 16 bits, não é possível obter o inteiro positivo +32768. Se
tentarmos realizar o complemento de dois com o menor número negativo, o processador 80x86 vai dar erro de
overflow na aritmética com sinal.

Talvez você esteja pensando que usar o bit mais significativo como flag de sinal e manter o número original fosse
uma solução mais lógica. Por exemplo, 0101 seria +5 e 1101 seria -5. Acontece que esta operação depende do
hardware. Para o processador, a negação (ou complemento, ou inversão) dos bits é fácil e rápida de ser realizada.
Para o programdor, não é preciso realizá-la bit a bit pois o 80x86 possui a instrução NEG que trata de todos os bits.

As operações com números negativos não é problema. Imagine a operação de soma com os números +5 e -5, sendo
que o -5 foi obtido com o sistema de complemento de dois:

1 1111 1111
5d 0000 0101
-5d + 1111 1011
------------
1 0000 0000

Os dígitos em vermelho são os famosos "vai um", que acontecem quando somamos dois bits de valor 1. O bit em
verde é o bit que excedeu o comprimento de oito bits, chamado de carry (o excedente). Se ignorarmos o carry, o
resultado está absolutamente correto, pois 5 + (-5) = 0. E é exatamente assim que o processador opera.

Não custa repetir que, os dados representado por um conjunto de bits dependem inteiramente do contexto. Os oito
bits do valor binário 11000000b podem representar um caracter ASCII, o valor decimal sem sinal 192, o valor
decimal com sinal -64, etc. Como programador, é sua a responsabilidade de usar os dados mantendo sua
consistência.

Extensão com Sinal e Extensão com Zeros (continua)

Como os inteiros no formato de complemento de dois têm um comprimento fixo, surge um pequeno problema. O
que acontece quando for preciso transformar um valor de complemento de dois de 8 bits num valor de 16 bits? Este
problema, e seu oposto (a transformação de um valor de 16 bits num de 8 bits), pode ser resolvido através das
operações de extensão e contração com sinal. O 80x86 trabalha com valores de comprimento fixo, mesmo quando
estiver processando números binários sem sinal. A extensão com zeros permite converter pequenos valores sem
sinal em valores maiores sem sinal.

Vamos a um exemplo, considerando o valor -64. O valor de complemento de dois para este número é 0C0h. O
equivalente de 16 bits deste número é 0FFC0h. Agora considere o valor +64. As versões de 8 e de 16 bits deste
valor são 40h e 0040h. A diferença entre os números de 8 e de 16 bits com sinal pode ser definida com a seguinte
regra: "Se o número for negativo, o byte mais significativo do número de 16 bits contém 0FFh; se o número for
positivo, o byte mais significativo do número de 16 bits é zero".

11
Para fazer a extensão com sinal de um valor com qualquer número de bits para um número maior de bits, basta
copiar o bit de sinal para todos os bits adicionais. Por exemplo, para ampliar um número de 8 bits com sinal para um
número de 16 bits com sinal, só é preciso copiar o bit 7 do número de oito bits para os bits de 8 a 15 do número de
16 bits. Para ampliar um número de 16 bits para um número de double word (32 bits), simplesmente copie o bit 15
para os bits de 16 a 31 do double word.

A extensão com sinal é necessária quando manipulamos valores com sinal de comprimentos diferentes. É comum
precisarmos somar uma quantidade em byte com uma quantidade em word. Neste caso, antes de efetuar a operação,
será preciso transformar a quantidade byte numa quantidade word. Outras operações, em particular a multiplicação
e a divisão, podem necessitar uma extensão com sinal para 32 bits. É óbvio que não é necessário fazer a extensão
com sinal para valores sem sinal. São exemplos de extensão com sinal:

8 bits 80h 28h --- ---


16 bits FF80h 0028h 1020h 8088h
32 bits FFFFFF80h 00000028h 00001020h FFFF8088h

Para ampliar números sem sinal basta fazer a extensão com zeros, um processo muito simples de zerar os bytes
adicionais. Veja abaixo:

8 bits 80h 28h --- ---


16 bits 0080h 0028h 1020h 8088h
32 bits 00000080h 00000028h 00001020h 00008088h

A contração com sinal, ou seja, converter um valor com determinado número de bits para um valor idêntico com um
número menor de bits é um processo um pouco mais complicado. A extensão com sinal sempre é possível, já a
contração com sinal nem sempre o é. Por exemplo, o valor decimal -448, representado como hexadecimal de 16
bits, é 0FE40h. Neste caso, é impossível obter este valor com apenas 8 bits, ou seja, a contração com sinal não é
possível pois perderíamos o valor original. Aliás, este é um exemplo de overflow que pode ocorrer numa conversão
impossível.

Para avaliar se é possível realizar uma contração com sinal é preciso analisar o(s) byte(s) mais significativos que
deverão ser descartados: todos precisam conter zero ou 0FFh. Se forem encontrados quaisquer outros valores, não
será possível fazer uma contração sem overflow. Além disso, o bit mais significativo do valor resultante precisa
coincidir com cada bit removido do número. Veja os exemplos:

16 bits 8 bits Observação


FF80h 80h OK
0040h 40h OK
FE40h --- Overflow
0100h --- Overflow

12
Comentários

Antes de começar os exercícios propostos, leia o texto adicional Asm - Lógica Booleana, que é uma outra
abordagem das operações lógicas com bits. Existem dois aplicativos escritos em Object Pascal (Delphi) pelo
Randall Hyde que traduzi para o Português e recompilei. Um deles é uma calculadora de operações lógicas
(download do executável com código fonte) e o outro faz extensões com sinal e com zeros (download do executável
com código fonte).

Treine exaustivamente a notação decimal, binária e hexadecimal. Faça conversões de binário para hexadecimal e de
hexadecimal para binário. Além disso, complete a tabela construída com valores decimais e binários com os
respectivos valores hexadecimais. Faça complemento de dois de vários valores e teste os menores números
negativos de 8, 16 e 32 bits. Faça extensões e contrações de valores com e sem sinal. E quer saber mais? Faça tudo
usando lápis e papel - calculadora só para conferir os resultados. Esta história de bits precisa ser incorporada!

13
REPRESENTAÇÃO DE DADOS III
Deslocamentos e Rotações

Outro conjunto de operações lógicas que podem ser aplicadas em strings de bits são o deslocamento (shift) e a
rotação (rotate). As duas categorias ainda podem ser subdivididas em deslocamento para a esquerda (left shift),
deslocamento para a direita (right shift), rotação para a esquerda (left rotate) e rotação para a direita (right rotate).
Estas operações se revelaram extremamente úteis para os programadores da linguagem Assembly.

A operação de deslocamento para a esquerda move uma posição para a esquerda


cada um dos bits de uma string de bits (veja ao lado). O bit zero é deslocado para a
posição 1, o da posição 1 para a posição 2, etc. Surgem naturalmente duas
perguntas: "O que vai para o bit zero?" e "Para onde vai o bit 7?" Bem, isto
Shift para a esquerda
depende do contexto. Nós colocaremos um bit zero na posição zero e o bit sete "cai
fora" nesta operação.

Observe que, deslocar o valor para a esquerda é o mesmo que multiplicá-lo pela sua base (ou radix). Por exemplo,
deslocar um número decimal para a esquerda em uma posição (adicionando um zero à direita do número) o
multiplica por 10 (a sua base):

1234 SHL 1 = 12340 (SHL 1 = shift esquerda 1 posição)

Como a base de um número binário é dois, o deslocamento em uma posição para a esquerda multiplica-o por 2. Se
deslocarmos um valor binário duas vezes para a esquerda, ele é multiplicado duas vezes por 2, ou seja, é
multiplicado por 4. Se o deslocarmos três vezes, será multiplicado por 8 (2*2*2). Como regra, se deslocarmos um
valor binário para a esquerda n vezes, isto o multiplicará por 2^n (2n ou 2 elevado a n).

Uma operação de shift para a direita funciona do mesmo modo que a anterior,
exceto que os dados são deslocados na direção oposta. O bit sete é movido para a
posição seis, o bit seis para a cinco e assim sucessivamente. Numa operação de
deslocamento para a direita, introduzimos um zero no bit sete e o bit zero será
Shift para a direita
descartado.

Como o deslocamento para a esquerda equivale a uma multiplicação pela base, não é de se admirar que um
deslocamento para a direita equivale a uma divisão pela base. No sistema binário, se fizermos n deslocamentos para
a direita, o valor será dividido por 2n.

Existe um problema relacionado com a divisão efetuada por um shift para a direita: um shift para a direita equivale
a uma divisão de um número sem sinal por 2. Por exemplo, se deslocarmos uma posição para a direita a
representação sem sinal de 254 (0FEh), obtemos 127 (07Fh), exatamente o esperado. Entretanto, se deslocarmos
uma posição para a direita a representação binária de -2 (0FEh), obtemos 127 (07Fh), o que não está correto. Este
problema ocorre porque estamos introduzindo zero no bit sete. Se o bit sete contém 1 antes do deslocamento,
indicativo de número negativo nos inteiros com sinal, e depois recebe zero, estamos alterando o sinal deste número
(que passa de negativo para positivo). Como este não é o propósito da divisão... dá erro.

Para usar um shift para a direita como um operador de divisão, antes é preciso
definir uma terceira operação de deslocamento: o deslocamento aritmético para a
direita (arithmetic shift right). Um shift aritmético para a direita funciona como o
shift para a direita normal, com uma exceção: ao invés de deslocar o bit sete para
Shift artimético para a direita a posição seis, este bit é deixado intacto, ou seja, o bit sete não é zerado.

Isto geralmente produz o resultado esperado. Por exemplo, fazendo um shift aritmético para a direita com -2
(0FEh), obtemos -1 (0FFh). Uma coisa, no entanto, não pode ser esquecida: esta operação sempre arredonda os
14
números para o inteiro que seja menor ou igual ao resultado, ou seja, arredonda para baixo. Um shift artimético para
a direita com 5 dá como resultado 2. Mas preste atenção. Um shift aritmético para a direita com -1 dá como
resultado -1, e não zero! O arredondamento se faz na direção do menor valor e -1 é menor do que 0. Este não é um
"bug" no shift aritmético para a direita, é apenas como a divisão de inteiros foi definida.

Outro para muito útil é a rotação para a esquerda e para a direita. Estas operações se comportam como os
deslocamentos, com uma diferença importante: o bit que sai numa extremidade entra na extremidade oposta.

Rotação para a esquerda Rotação para a direita

Campos Bit e Dados Compactados

Apesar do 80x86 operar eficientemente com tipos de dado byte, word e double word, ocasionalmente teremos que
trabalhar com tipos de dado que usam um número bits diferente do habitual (8, 16 ou 32). Por exemplo, imagine
uma data na forma "2/4/98". São três valores
numéricos que representam uma data: valores para
o dia, o mês e o ano. Os dias variam de 1 a 31, o
que consome 5 bits (valor máximo de 32) para
representar a entrada dos dias. Os meses variam de Campos bit especiais
1 a 12. Portanto, 4 bits são suficientes (valor
máximo de 16). O valor para o ano, imaginando que trabalhemos com valores variando de 0 a 99, precisam de 7 bits
(que podem representar até o valor 128). Somando os bits necessários, chega-se a 16 bits, o mesmo que 2 bytes. Em
outras palavras, podemos acomodar nossos dados de data em dois bytes ao invés de três se usássemos um byte para
cada um dos campos dia, mês e ano. Isto economiza um byte de memória para cada data armazenada, o que pode
representar uma economia substancial se tivermos que armazenar milhões de datas. O arranjo dos bits pode ser visto
na figura acima.

DDDDD representam os 5 bits reservados para o valor do dia, MMMM representam os quatro bits para armazenar o
mês e YYYYYYY são os sete bits reservados para o valor do ano. Cada coleção de bits representando um item de
data é denominado de campo bit. 2 de Abril de 1998 seria representado respectivamente pelos bits 00010 0100
1100010, ou seja, 1262h.

DDDDD MMMM AAAAAAA


00010 0100 1100010 ou 1262h
2 4 98

Apesar dos valores compactados serem muito eficientes para economizar espaço (isto é, eficientes em termos de uso
de memória), computacionalmente são muito ineficientes. A razão é que dependem de instruções extras para
descompactar os dados compactados nos diversos campos bit. Estas operações adicionais consomem tempo e bytes
adicionais para armazenar as instruções. Portanto, o planejamento prévio e o custo/benefício precisa ser bem
analisado.

Existem inúmeros exemplos de tipos de dados compactados. Pode-se compactar oito valores booleanos num único
byte, pode-se acomodar dois dígitos BCD num byte, etc.

O conjunto de caracteres ASCII

O conjunto de caracteres ASCII (excluindo-se os caracteres expandidos definidos pela IBM) é dividido em quatro
grupos de 32 caracteres. O primeiro grupo, códigos ASCII de 0 a 1Fh (31), formam um conjunto especial de
caracteres não imprimíveis chamados caracteres de controle. São chamados de caracteres de controle porque
15
realizam várias operações de controle de impressão/display ao invés de mostrarem símbolos. Exemplos incluem o
retorno de carro (carriage return), que posiciona o cursor no lado esquerdo da linha atual, avanço de linha (line
feed), que move o cursor uma linha para baixo no dispositivo de saída e o retorno (back space), que move o cursor
uma posição para a esquerda. Infelizmente os caracteres de controle realizam operações diferentes dependendo do
dispositivo de saída. A padronização entre os dispositivos de saída é precária. Para saber exatamente como se
comporta determinado caracter de controle em relação a um determinado dispositivo de saída, é preciso consultar o
manual do dispositivo.

O segundo grupo de 32 códigos de caracteres ASCII inclui vários símbolos de pontuação, caracteres especiais e
dígitos numéricos. Os caracteres mais conhecidos deste grupo são o espaço (código ASCII 20h) e os dígitos
numéricos (códigos ASCII de 30h a 39h). Lembre-se de que os dígitos numéricos diferem dos seus valores
numéricos apenas no nibble mais significativo. Se subtrairmos 30h do código ASCII de qualquer um dos dígitos,
obtemos o equivalente numérico deste dígito.

O terceiro grupo de caracteres ASCII é reservado para os caracteres alfabéticos maiúsculos. Os códigos ASCII para
os caracteres de A a Z ficam no intervalo 41h a 5Ah (65 a 90). Como só existem 26 caracteres alfabéticos
diferentes, os seis códigos restantes são de vários símbolos especiais.

Finalmente, o quarto grupo de 32 códigos de caracteres ASCII são reservdos


para os símbolos alfabéticos minúsculos, cinco símbolos adicionais especiais e
outro caracter de controle (delete). Observe que os símbolos dos caracteres
minúsculos usam os códigos ASCII de 61h a 7Ah. Se convertermos os códigos
dos caracteres maiúsculos e minúsculos para binário, é possível verificar que
os símbolos maiúsculos diferem dos seus correspondentes minúsculos em
apenas um bit. Por exemplo, veja ao lado os códigos para os caracteres "E" e
Caracteres maiúsculos e minúsculos "e".
diferem em apenas um bit
A única diferença entre estes dois códigos reside no bit 5. Caracteres
maiúsculos sempre possuem 0 no bit cinco, os minúsculos sempre possuem 1 no bit cinco. Podemos usar esta
característica para converter rapidamente maiúsculas em minúsculas e vice versa. Se o caracter for maiúsculo, para
forçá-lo para minúsculo basta setar o bit cinco para 1. Se o caracter for minúsculo, para forçá-lo para maiúsculo
basta setar o bit cinco para 0.

Na realidade, os bits cinco e seis determinam o grupo ao qual o caracter ASCII pertence:

Bit 6 Bit 5 Grupo


0 0 Caracteres de controle
0 1 Dígitos e caracteres de pontuação
1 0 Maiúsculos e especiais
1 1 Minúsculos e especiais

Podemos, por exemplo, transformar qualquer caracter maiúsculo ou minúsculo (ou especial) no seu caracter de
controle equivalente apenas zerando os bits cinco e seis.

Caract Decimal Hexa Agora observe ao lado os códigos ASCII para os caracteres dos dígitos numéricos. A
0 48 30h representação decimal destes códigos ASCII não esclarece grande coisa mas, a
1 49 31h representação hexadecimal revela algo muito importante - o nibble menos significativo
2 50 32h do código ASCII é o equivalente binário do número representado. Zerando o nibble mais
significativo, converte-se o código do caracter para a sua representação binária.
3 51 33h
Inversamente, é possível converter um valor binário do intervalo de 0 a 9 para a sua
4 52 34h representação ASCII simplesmente setando o nibble mais significativo em três. Note que
5 53 35h é possível usar a operação lógica AND para forçar os bits mais significativos para zero e
6 54 36h usar a operação lógica OR para forçar os bits mais significativos para 0011 (três).
7 55 37h
16
8 56 38h
9 57 39h
Lembre-se de que não é possível transformar uma string de caracteres numéricos na sua representação binária
correspondente simplesmente zerando o nibble mais significativo de cada dígito da string. Transformando 123 (31h
32h 33h) desta maneira resulta em três bytes: 010203h, e não no valor correto que é 7Bh. Transformar uma string de
dígitos requer um pouco mais de sofisticação. A transformação explicada acima só serve para dígitos únicos.

O bit sete no ASCII padrão é sempre zero. Isto significa que o conjunto de caracteres ASCII utiliza apenas a metade
dos códigos possíveis num byte de oito bits. A IBM usa os 128 códigos restantes para vários caracteres especiais
(com acento, etc), símbolos matemáticos e caracteres de desenho de linhas. Deve ficar claro que estes caracteres
extras são uma extensão não padronizada do conjunto de caracteres ASCII. É claro que o nome IBM tem peso e a
maioria dos computadores baseados no 80x86 e as impressoras acabaram incorporando os caracteres adicionais da
IBM.

Apesar do fato de que é um padrão, codificar seus dados usando simplesmente os caracteres padrão ASCII não
garante a compatibilidade entre os sistemas se bem que, hoje em dia, dificilmente encontraremos problemas. Como
usaremos com frequência os caracteres ASCII em Assembly, seria interessante guardar de cabeça alguns códigos
ASCII importantes, como o do "A", do "a" e do "0".

Comentários

Brinque com os bits até não poder mais e parabéns por entrar no clube dos chamados "escovadores de bits". Dou-
lhe as boas vindas, mas prepare-se... no capítulo 2 vem chumbo grosso. Se você não estiver firme nestes assuntos,
hmmmmmm...

Para este tópico do capítulo 1 também existe um aplicativo escrito em Object Pascal (Delphi) pelo Randall Hyde
que traduzi para o Português, adaptei para o exemplo acima e recompilei. É um compactador de dados para datas no
formato dia/mês/ano. Você pode fazer o download do executável com código fonte aqui na Aldeia.

De qualquer modo, é melhor encarar o laboratório do capítulo 1, assunto do próximo módulo, antes de querer
continuar. As ferramentas indicadas para download devem ajudar.

17
LABORATÓRIO
Praticamente todos os capítulos possuem um módulo de laboratório, uma espécie de guia de estudo. Se o guia não
apresentar todas as informações necessárias para resolver os exercícios propostos, procure-as no capítulo ao qual ele
se refere.

As questões são numeradas sequencialmente de acordo com o capítulo. Assim, por exemplo, Q01.08 representa a
oitava questão do capítulo 1. As respostas podem ser visualizadas clicando e arrastando o mouse sobre a área
sombreada (comece sempre do canto superior esquerdo e vá para o canto inferior direito). Aconselho a responder
as questões antes de verificar as respostas corretas, senão, qual é a vantagem que Maria leva? Exercício é para ser
resolvido ;)))

Representação de dados numéricos

O sistema decimal é, sem sombra de dúvida, o sistema numérico mais utilizado. Cada dígito à esquerda do ponto
"decimal" representa um valor que varia de 0 a 9 vezes sucessivas potências positivas de dez. Da mesma forma,
cada dígito à direita do ponto decimal representa um valor de sucessivas potências negativas de dez. Exemplos:

• 1234.567 é 1*1000 + 2*100 + 3*10 + 4 + 5*0.1 + 6 * 0.01 + 7 * 0.001. Isto também equivale ao valor
1*103 + 2 *102 + 3*101 + 4*100 + 5*10-1 + 6*10-2 + 7*10-3
• 5.23e+5 é 5*105 + 2*104 + 3*103

Q01.01

• Qual é a representação em "potência de dez" de 7009.001?

7*103 + 0*102 + 0*101 + 9*100 + 0*10-1 + 0*10-2 + 1*10-3

o que, simplificado, será

7*103 + 9*100 + 1*10-3

O sistema de numeração binário funciona de um modo parecido com o do sistema decimal. Ao invés de sucessivas
potências de dez, usa sucessivas potências de dois. Ao invés de dígitos de 0 a 9, permite apenas os dígitos 0 e 1.
Exemplos:

• 1001 é 1*23 + 1*20 (o equivalente decimal é nove)


• 110.11 é 1*22 + 1*21 + 1*2-1 + 1*2-2 (o equivalente decimal é 6.75)

Q01.02

• Qual é a representação em "potência de dois" de 1101101.1?

1*26 + 1*25 + 1*23 + 1*22 + 1*20 + 1*2-1

Q01.03

• Qual é o equivalente decimal de 1101101.1?

O equivalente decimal de 1101101.1 é 109.5

18
Na linguagem Assembly, os números binários geralmente são agrupados em blocos de quatro, oito, dezesseis e
trinta e dois "bits" (binary digits - dígitos binários). Além disso, a maioria dos valores usados no Assembly são
inteiros. Neste texto usaremos a convenção normal de numeração dos bits, de 0 até n-1, onde n é o total de bits
presentes no valor binário. O bit menos significativo (ou de ordem baixa - O.B.) é sempre o bit número zero e o bit
mais significativo (ou de ordem alta - O.A.) é sempre o bit número n-1 de um número binário.

Q01.04

• Num número binário de 16 bits, qual é o bit O.A.?

O bit número 15 é o mais significativo.

Programadores de linguagem Assembly, particularmente para o 80x86, com frequência trabalham com strings de
bits de quatro, oito, dezesseis ou trinta e dois bits de comprimento. É por isso que são usados vários nomes especiais
para os dados binários dos referidos comprimentos. São eles, respectivamente: nibbles, bytes, words e double words
(words duplos).

Q01.05

• Qual é o número do bit mais significativo num word? E num duplo word?

O bit mais significativo num word é o bit 15.


O bit mais significativo num double word é o bit 31.

Q01.06

• Quantos bits há num byte?

Um byte é composto por 8 bits.

Q01.07

• Quantos bytes há num duplo word?

Um double word é composto por 4 bytes.

Q01.08

• Qual é o número do bit menos significativo num nibble?

O bit menos significativo num nibble (como em qualquer


outro grupo) é o bit 0.

Na prática, os números binários são muito desajeitados para serem usados. É por isso que a maioria dos
programadores utiliza o sistema de numeração hexadecimal. O sistema de numeração hexadecimal (também
conhecido simplesmente por hexa) fornece notações muito mais compactas que o sistema binário e a conversão
entre hexa e binário é bastante simples quando se usa a seguinte tabela:

Hexadecimal Binário
0 0000
1 0001
2 0010
3 0011
19
4 0100
5 0101
6 0110
7 0111
8 1000
9 1001
A 1010
B 1011
C 1100
D 1101
E 1110
F 1111

Q01.09

• Qual é o equivalente binário do hexa FEDC?

FEDC = 1111 1110 1101 1100

Q01.10

• Qual é o equivalente hexa do binário 11011010010010?

0011 0110 1001 0010 = 3692h

Q01.11

• Qual é o equivalente hexa do binário 100010010000101?

0100 0100 1000 0101 = 4485h

Q01.12

• Qual é o valor do bit O.A. do número de 16 bits 7F8C?

É zero porque 7, o byte de O.A., é igual a 0111 1111

As quatro operações lógicas mais importantes são AND, OR, XOR e NOT. As tabelas lógicas para estas operações
são:

AND 0 1 OR 0 1
0 0 0 0 0 1
1 0 1 1 1 1
XOR 0 1 NOT 0 1
0 0 1 NOT 1 0
1 1 0

AND, OR e XOR são funções diádicas (precisam de dois operandos) e NOT é monádica. Por exemplo:

• 1 AND 1 = 1
20
• 0 OR 0 = 0
• 1 XOR 0 = 1
• NOT 1 = 0

De acordo com as tabelas verdade acima, estas quatro operações atuam apenas em operandos de bit único. Em
Assembly, no entanto, com frequência é preciso trabalhar com strings de bits (bytes, words, etc), o que torna
necessária uma extensão destas funções. Para trabalhar com strings de bits é preciso que ambas tenham o mesmo
comprimento. Para ajustá-lo, basta adicionar zeros à esquerda da string menor. Depois disso, basta realizar a
operação desejada bit a bit:

• 100010 AND 1110001 = 010 0010 AND 111 0001 = 010 0000
• 11110000 OR 0101010 = 1111 0000 OR 0010 1010 = 1111 1010
• 10100101 XOR 11110000 = 1010 0101 XOR 1111 0000 = 0101 0101
• NOT F4h = NOT 1111 0100 = 0000 1011 = 0Bh

Q01.13

• Calcule (FBh AND 54h)

FBh = 1111 1011


AND 54h = 0101 0100
-------------
0101 0000 = 50h

Q01.14

• Calcule (12h OR 34h)

12h = 0001 0010


OR 34h = 0011 0100
-----------
0011 0110 = 36h

Q01.15

• Calcule (5Ah XOR 5Ah)

5Ah = 0101 1010


XOR 5Ah = 0101 1010
-------------
0000 0000 = 0h

Q01.16

• Calcule (NOT 4Fh)

4Fh = 0100 1111


NOT = 1011 0000 = B0h

Você ainda se lembra da transformação de números positivos em negativos (e vice versa) usando o complemento de
dois? Só para refrescar a memória:

1. Transformar o número em binário


2. Inverter todos os bits com NOT
3. Somar 1 ao resultado
21
4. Converter o binário para o sistema original

Q01.17

• Transforme -FFFFh no número positivo correspondente.

FFFFh = 1111 1111 1111 1111


NOT = 0000 0000 0000 0000
+ 1 = 0000 0000 0000 0001 = 1h

Q01.18

• Transforme -(FACEh AND F0F0h) no número positivo correspondente.

FACEh = 1111 1010 1100 1110


F0F0h = 1111 0000 1111 0000
---------------------
AND 1111 0000 1100 0000
NOT 0000 1111 0011 1111
+1 0000 1111 0100 0000
HEXA 0 F 4 0

Para poder realizar operações com valores representados por um número de bits diferente (por exemplo, somar um
byte com um word), é preciso expandir o valor representado com menos bits. Reveja as regras:

• A extensão com sinal do valor de oito bits FCh para 16 bits é FFFCh.
• A extensão com sinal do valor de 8 bits 7Fh para 16 bits é 007Fh
• A extensão com sinal do valor de 16 bits 8000h para 32 bits é FFFFFF80h

Q01.19

• Faça a extensão com sinal do valor de 8 bits 5Fh para 16 bits.

5Fh = 0101 1111


0000 0000 0101 1111
hexa 0 0 5 F

Q01.20

• Faça a extensão com sinal do valor de 8 bits 91h para 16 bits.

91h = 1001 0001


1111 1111 1001 0001
hexa F F 9 1

Q01.21

• Faça a extensão com sinal do valor de 16 bits 91h para 32 bits.

91h = 0000 0000 1001 0001


0000 0000 0000 0000 0000 0000 1001 0001
hexa 0 0 0 0 0 0 9 1

Q01.22

22
• Faça a extensão com sinal do resultado de 16 bits de 01FFh AND FF81h) para 32 bits.

01FFh = 0000 0001 1111 1111


FF81h = 1111 1111 1000 0001
---------------------
AND = 0000 0001 1000 0001

0181 hexa de 16 bits


0000 0181 hexa de 32 bits

A contração, para reduzir o número de bits de um valor, só pode ser efetuada se o resultado mantiver o valor
original. O princípio da contração com sinal é o mesmo da expansão com sinal, sendo apenas a operação inversa.

Q01.23

• É possível fazer a contração do valor de 16 bits FF80h para uma valor de 8 bits?

FF80h = 1111 1111 1000 0000


80h = 1000 0000

Sim, o bit mais significante permanece 1 e


o valor original é mantido.

Q01.24

• É possível fazer a contração do valor de 16 bits 0102h para uma valor de 8 bits?

0102h = 0000 0001 0000 0010


02h = 0000 0010

Não, o bit mais significante permanece 0 mas


o valor original é perdido.

Lembra dos campos bit e dos dados compactados? O exemplo era um campo bit para guardar datas. Os bits D eram
para o dia, os bits M para o mês e os A para o ano - e tudo cabe direitinho em 16 bits. Veja abaixo:

Lembra também das operações de shift (deslocamento) e rotate (rotação)? Só para refrescar a memória, aqui estão
os diagramas:

Shift para a direita Shift para a esquerda

Shift artimético para a direita Rotação para a esquerda

Q01.25

23
• Como extrair o bit de sinal de um número usando uma rotação para a esquerda e uma instrução AND?

O número de bits não importa. Digamos que sejam oito.


Basta fazer um ROL 1 seguido de um AND 1. O bit de sinal é
identificado por S:

Sxxx xxxx
ROL 1 = xxxx xxxS
1 = 0000 0001
AND = 0000 000S

Q01.26

• Numa data armazenada no campo bit acima, quais as operações necessárias para extrair o valor do ano?

Os bits correspondentes ao ano ocupam as posições de 0 a 6.


Basta fazer um AND 7Fh:

xxxx xxxx xAAA AAAA


7Fh = 0000 0000 0111 1111
AND = 0000 0000 0AAA AAAA

Q01.27

• Numa data armazenada no campo bit acima, quais as operações necessárias para extrair o valor do mês?

Os bits correspondentes ao mês ocupam as posições de 7 a 10.


Precisamos fazer um AND 78h seguido de 7 shifts para a direita (SHR 7):

xxxx xMMM Mxxx xxxx


78h = 0000 0111 1000 0000
AND = 0000 0MMM M000 0000
SHR 7 = 0000 0000 0000 MMMM

Q01.28

• Numa data armazenada no campo bit acima, quais as operações necessárias para extrair o valor do dia?

Os bits correspondentes ao dia ocupam as posições de 11 a 15.


Umas das possibilidades é fazer 11 shifts para a esquerda (SHR 12):
DDDD Dxxx xxxx xxxx
0DDD DDxx xxxx xxxx
00DD DDDx xxxx xxxx
...
0000 0000 000D DDDD

Outra possibilidade é fazer um AND F800h seguido por cinco rotações


para a esquerda (ROL 5):
DDDD Dxxx xxxx xxxx
1111 1000 0000 0000
AND = DDDD D000 0000 0000
ROL 1 = DDDD 0000 0000 000D
ROL 1 = DDD0 0000 0000 00DD
...
ROL 1 = 0000 0000 000D DDDD

A terceira possibilidade é fazer cinco rotações para a esquerda


seguida de um AND 1Fh:
24
DDDD Dxxx xxxx xxxx
ROL 5 = xxxx xxxx xxxD DDDD
1Fh = 0000 0000 0001 1111
AND = 0000 0000 000D DDDD

Q01.29

• Numa data armazenada no campo bit acima, qual é o padrão de bits para a data 12.01.04?

DDDD DMMM MAAA AAAA


0110 0000 1000 0100
hexa = 6 0 8 4

Q01.29

• Quais as operações que você usaria para inserir o dia 20 na data acima?

DDDD DMMM MAAA AAAA


7FFh = 0000 0111 1111 1111
AND = 0000 0MMM MAAA AAAA remove o dia anterior
ROL 5 = MMMM AAAA AAA0 0000 põe na posição
14h = 0000 0000 0001 0100 dia 20d = 14h
OR = MMMM AAAA AAA1 0100 põe dia
ROR 5 = 1010 0MMM MAAA AAAA volta para posição original

O conjunto de caracteres ASCII é provavelmente o código mais universal que existe hoje em dia. O conjunto padrão
de caracteres ASCII inclui 128 códigos diferentes, arranjados em quatro grupos de 32 códigos. Os dígitos de 0 a 9
possuem os códigos de 30h a 39h. Estes grupos podem ser identificados através dos bits 5 e 6:

Bit 6 Bit 5 Grupo


0 0 Caracteres de controle
0 1 Dígitos e caracteres de pontuação
1 0 Maiúsculos e especiais
1 1 Minúsculos e especiais

Q01.30

• Qual operação lógica é usada para converter minúsculas para maiúsculas? E para converter maiúsculas em
minúsculas? E para transformar o caracter "5" no seu valor numérico? E o valor numérico 5 para caracter?

7654 3210
minúsculas = 011x xxxx
5Fh = 0101 1111
AND = 010x xxxx = maiúsculas

maiúsculas = 010x xxxx


20h = 0010 0000
OR = 011x xxxx = minúsculas

caracter 5 = 0011 0101 (35h)


Fh = 0000 1111
AND = 0000 0101 = valor 5

valor 5 = 0000 0101


30h = 0011 0000
OR = 0011 0101 = 35h
25
Comentários

Estas 30 questões cobrem os principais conceitos explicados no capítulo 1. Se você conseguiu responder
corretamente pelo menos 24 delas, considere-se aprovado. Independente da média obtida, revise cuidadosamente as
questões que você errou - com erros se aprende mais do que com acertos ;))) Analise se o erro foi devido à lógica ou
à falta de concentração (bits trocados, etc). Este levantamento ajudará a melhorar sua performance.

Depois de analisar e corrigir as questões que você não acertou, é hora de entrar de sola na álgebra booleana. Boa
sorte!

26

Interesses relacionados