Assembly NumaBoa - Capítulo 1

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:

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:

HexadecimalBinário
00000
10001
20010
30011
40100
50101
60110
70111
81000
91001
A1010
B1011
C1100
D1101
E1110
F1111

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.

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:

AND01
000
101

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:

OR01
001
111

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 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:

XOR01
001
110

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 sequê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 01
NOT 10

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
	        -----------
		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 complemto 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:

	decimal -5	binário	1111 1011		(0FBh)
	inverter bits		0000 0100		(04h)
	somar 1			0000 0101		(05h)

Agora oberve 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

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".

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 bits80h28h------
16 bitsFF80h0028h1020h8088h
32 bitsFFFFFF80h00000028h00001020hFFFF8088h

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 bits80h28h------
16 bits0080h0028h1020h8088h
32 bits00000080h00000028h00001020h00008088h

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 bits8 bitsObservação
FF80h80hOK
0040h40hOK
FE40h---Overflow
0100h---Overflow

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!



| AAAA | Página Inicial | Mapa do Site | Novidades | Busca | Indique esta página | Mestre da Teia | Voltar |
| Localizador || @ Info NumaBoa > Assembly NumaBoa > Representação de dados > Numeração hexadecimal > Deslocamentos e rotações
Autoria: Randall Hyde - Art of Assembly Language Programming. Tradução: vovó Vicki

webdesign sobMedida by vickiSoft - /informatica/assembly/cap1_2.php (09.01.04) versão 1.0 de 12.02.04
Licença Creative Commons 1998-2006 Aldeia NumaBoa
Exceto onde especificamente declarado, todo material deste site é disponibilizado de acordo com a Licença Creative Commons.