
                               ASSEMBLY
                     Como so feitos os Demos, Musicas, etc ..
                                Aula nr. 20 de 26

    Impressionante como as  demonstraes grficas (DEMOS) conseguem
ser  to  rpidas  com  todas  aquelas  transformaes   geomtricas
(objetos  movimentando-se  no  espao  tridimensional),  musicas  em
background, etc...  A complexidade sugere a utilizao de rotinas em
ponto-flutuante    para    os    calculos    "cabeludos"...     Opa!
Ponto-flutuante?!   Mas  isso  muito lerdo!!!!  Toma muito tempo de
CPU...  E nem sempre o  feliz proprietrio de um microcomputador tem
um 486DX ou um 386 com  co-processador!   Como    que  esses  caras
conseguem tanta velocidade?!

    A  resposta pode estar num mtodo conhecido como "aritimtica de
ponto-fixo", que  o objetivo deste texto!

    Imagine que possamos  escrever  um  nmero "quebrado" (com casas
decimais) da seguinte maneira:

  Ŀ
                                  
  
           parte inteira                 parte fracionria

    A "casa" mais a esquerda  o bit mais significativo, e a mais  a
direita o menos significativo.  Assim os 16 bits mais significativos
(parte  inteira)  nos diz a "parte inteira" do nmero (lgico, n?).
E os 16  bits  menos  significativos  (parte  fracionria) nos diz a
parte  fracionria  do  nmero (outra vez, lgico!).  De forma que o
bit menos significativo destes 32  bits   equivalente a 2 elevado a
potncia de -16 (ou seja: 1/65536). Eis um exemplo:

 Ŀ
   0000000000000000.1000000000000000b = 0.5 = 1/2                 
   0000000000000000.0100000000000000b = 0.25 = 1/4                
   0000000000000000.0010000000000000b = 0.125 = 1/8               
   0000000000000000.1110000000000000b = 0.875                     
   0000000000000001.1000000000000000b = 1.5 = 1 + 1/2             
   0000000000000011.0010010000111111b =
 (aprox.)                
   0000000000000000.1101110110110011b  cos(
/6)  0.866 (aprox.) 
 

    No sei se deu para entender, mas do bit menos significativo at
o mais significativo, o expoente vai aumentando, s que o bit  menos
significativo  tem expoente -16.  Assim, o bit 1 tem expoente -15, o
seguinte -14, etc...  at  o  ltimo,  15.   O  ponto  entre os dois
conjuntos  de  16  bits  foi  adicionado  apenas  para  facilitar  a
visualizao no exemplo acima.

    Ok... ento  possvel representar "nmeros quebrados"  em  dois
conjuntos de 16 bits... a pergunta : Pra que?!

    Aritimtica  com  nmeros inteiros sempre  mais rpida do que a
aritimtica com nmeros em ponto-flutuante.  Tendo co-processador ou
no!   Mesmo  que  vc  tenha   um  486DX4  100MHz,  os  calculos  em
ponto-flutuante sero mais lerdamente efetuados  do  que  os  mesmos
calculos  com  nmeros  inteiros  (usando os registradores da CPU!).
Neste ponto entra a  aritimtica  de  ponto-fixo  (note que o "ponto
decimal" no  muda  de  posio...).   Vejamos  o  que  acontece  se
somarmos dois nmeros em ponto fixo:

 Ŀ
   0.25 + 1.75 = 2.0                                              
                                                                  
     0000000000000000.0100000000000000b =    0.25                 
   + 0000000000000001.1100000000000000b =  + 1.75                 
                      
     0000000000000010.0000000000000000b =    2.00                 
 

    Realmente  simples...   apenas uma soma binria...  Suponha que
tenhamos um nmero em ponto fixo  no registrador EAX e outro no EDX.
O cdigo para somar os dois nmeros ficaria to simples quanto:

 Ŀ
   ADD     EAX,EDX                                                
 

    O  mesmo  ocorre na subtrao...  Lgicamente, a subtrao  uma
adico com o segundo  operando  complementado (complemento 2), ento
no h problemas em fazer:

 Ŀ
   SUB     EAX,EDX                                                
 

    A adio ou subtrao de dois nmeros em ponto fixo consome de 1
a  2  ciclos de mquina apenas, dependendo do processador... o mesmo
no ocorre com aritimtica em ponto-flutuante!

    A complicao comea a surgir na multiplicao e diviso de dois
nmeros em  ponto-fixo.   No  podemos  simplesmente  multiplicar ou
dividir como fazemos com a soma:

 Ŀ
     0000000000000001.0000000000000000                             
   * 0000000000000001.0000000000000000                             
                              
     0000000000000000.0000000000000000 + carry                     
 

    Nultiplicando 1 por 1 deveriamos  obter  1,  e no 0.  Vejamos a
multiplicao de dois valores menores que 1 e maiores que 0:

 Ŀ
     0000000000000000.100000000000000     0.5                      
   * 0000000000000000.100000000000000   * 0.5                      
                        
     0100000000000000.000000000000000  16384.0                     
 

    Hummm...  o  resultado  deveria  dar  0.25.   Se  dividirmos   o
resultado por 65536 (2^16) obteremos o resultado correto:

 Ŀ
     0100000000000000.000000000000000 >> 16 =                      
     0000000000000000.010000000000000       = 0.25                 
 

    Ahhh...  mas, e como ficam os nmeros maiores ou iguais a 1?!  A
instruo IMUL dos microprocessadores  386  ou superiores permitem a
multiplicao de dois inteiros de 32 bits resultando num inteiro  de
64  bits  (o  resultado  ficar  em  dois  registradores  de 32 bits
separados!).  Assim, para multiplicarmos  dois nmeros em ponto fixo
estabelecemos a seguinte regra:

 Ŀ
   resultado = (n1 * n2) / 65536           ou                     
   resultado = (n1 * n2) >> 16                                    
 

    Assim, retornando ao primeiro caso de multiplicao (em  notao
hexa agora!):

Ŀ
   0001.0000h * 0001.0000h = 000000010000.0000h                   
                                                                  
   Efetuando o shift de 16 bits para a direita:                   
                                                                  
   00010000.0000h >> 16 = 0001.0000h                              
                                                                  


    Em assembly isso seria to simples como:

 Ŀ
   PROC    FixedMul                                               
   ARG     m1:DWORD, m2:DWORD                                     
                                                                  
       mov     eax,m1                                             
       mov     ebx,m2                                             
       imul    ebx                                                
       shrd    eax,edx,16                                         
       ret                                                        
                                                                  
   ENDP                                                           
 

    A instruo IMUL, e  no  MUL,  foi  usada  porque os nmeros de
ponto fixo so sinalizados (o bit mais significativo    o  sinal!).
Vale  aqui a mesma regra de sinalizao para nmeros inteiros:  Se o
bit mais significativo  estiver  setado  o  nmero   negativo e seu
valor absoluto  obtido atravs do seu complemento (complemento  2).
Quanto a manipulao dos sinais numa multiplicao... deixe isso com
o IMUL! :)

    A diviso tambm tem as  suas complicaes... suponha a seguinte
diviso:

 Ŀ
    0001.0000h                                                     
    = 0000.0000h (resto = 0001.000h)                   
    0002.0000h                                                     
 

    A explicao deste resultado  simples:  estamos fazendo diviso
de dois nmeros inteiros...  Na  aritimtica inteira a diviso com o
dividendo menor que o divisor sempre resulta num quociente zero!

    Eis  a  soluo:   Se  o  divisor  est  deslocado  16 bits para
esquerda  (20000h    diferente  de  2,  certo!?),  ento precisamos
deslocar o dividendo 16 bits  para  esquerda  antes  de  fazermos  a
diviso!   Felizmente  os  processadores  386  e superiores permitem
divises com dividendos de 64bits  e  divisores de 32bits.  Assim, o
deslocamento  de  16  bits  para  esquerda  do   dividendo   no   
problemtica!

 Ŀ
   0001.0000h << 16 = 00010000.0000h                               
                                                                   
   00010000.0000h / 0002.0000h = 0000.8000h                        
                                                                   
       ou seja:                                                    
                                                                   
   1 / 2 = 0.5                                                     
 

    Eis a rotina em assembly que demonstra esse algorritmo:

 Ŀ
   PROC    FixedDiv                                               
   ARG     d1:DWORD, d2:DWORD                                     
                                                                  
       mov     eax,d1      ; pega dividendo                       
       mov     ebx,d2      ; pega divisor                         
                                                                  
       sub     edx,edx                                            
                                                                  
       shld    edx,eax,16                                         
       shl     eax,16                                             
                                                                  
       idiv    ebx                                                
       ret                                                        
                                                                  
   ENDP                                                           
 
                                                                                                          
