+-------------+
 ASSEMBLY XX 
+-------------+

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

    Isso tudo  muito interessante,  no?!  Hehehe... mas vou deixar
vc mais desesperado ainda:  A diviso  tem  um  outro  problema!   E
quanto aos sinais?!  O bit mais significativo de um inteiro pode ser
usado  para  sinalizar  o nmero (negativo = 1, positivo = 0), neste
caso teremos ainda que complementar o nmero para sabermos seu valor
absoluto.  Se simplesmente zeraramos EDX  e o bit mais significativo
estiver setado estaremos dividindo  um  nmero  positivo  por  outro
nmero  qualquer  (j  que  o  bit  mais  significativo  dos  64bits
resultantes ser 0!).  Vamos  complicar  mais  um pouquinho o cdigo
da diviso para sanar este problema:

 +-----------------------------------------------------------------+
   PROC    FixedDiv                                               
   ARG     d1:DWORD, d2:DWORD                                     
                                                                  
       sub     cl,cl       ; CL = flag                            
                           ; == 0 -> resultado positivo.          
                           ; != 0 -> resultado negativo.          
                                                                  
       mov     eax,d1      ; pega dividendo                       
                                                                  
       or      eax,eax     ;  negativo?!                         
       jns     @@no_chs1   ; no! ento no troca sinal!          
                                                                  
       neg     eax         ; ! ento troca o sinal e...          
       inc     cl          ; incrementa flag.                     
   @@no_chs1:                                                     
                                                                  
       mov     ebx,d2      ; pega divisor                         
                                                                  
       or      ebx,ebx     ;  negativo?!                         
       jns     @@no_chs2   ; no! ento no troca sinal!          
                                                                  
       neg     ebx         ; ! ento troca sinal e...            
       dec     cl          ; decrementa flag.                     
   @@no_chs2:                                                     
                                                                  
       sub     edx,edx                                            
                                                                  
       shld    edx,eax,16                                         
       shl     eax,16                                             
                                                                  
       div     ebx         ; diviso de valores positivos...      
                           ; ... no precisamos de idiv!          
                                                                  
       or      cl,cl       ; flag == 0?                           
       jz      @@no_chs3   ; sim! resultado  positivo.           
                                                                  
       neg     eax         ; no! resultado  negativo...         
                           ; ... troca de sinal!                  
   @@no_chs3:                                                     
       ret                                                        
                                                                  
   ENDP                                                           
 +-----------------------------------------------------------------+

    Se ambos os valores so negativos (d1 e d2)  ento  o  resultado
ser  positivo.   Note que se d1  negativo CL  incrementado.  Logo
depois... se d2 tambm   negativo,  CL  decrementado (retornando a
0).  A rotina ento efetuar diviso de valores positivos e  somente
no final  que mudar o sinal do resultado, se for necessrio!

    Uma  considerao  a  fazer  :   Como "transformo" um nmero em
ponto flutuante em ponto-fixo e vice-versa?!

    Comecemos  pela transformao de nmeros inteiros em ponto-fixo:
O nosso ponto-fixo est situado exatamente no meio de uma doubleword
(DWORD), o que  nos  d  16  bits  de  parte  inteira  e 16 de parte
fracionria.  A transformao de um nmero inteiro para ponto-fixo 
mais que simples:

 +-----------------------------------------------------------------+
   FixP = I * 65536          ou                                   
   FixP = I << 16                                                 
                                                                  
   onde FixP = Fixed Point (Ponto fixo)                           
        I    = Integer (Inteiro)                                  
 +-----------------------------------------------------------------+

    Desta forma os 16 bits superiores contero o nmero inteiro e os
16  bits  inferiores  estaro  zerados  (um  inteiro  no  tem parte
fracionria, tem?!).

    Se quisermos obter a  componente  inteira  de um nmero de ponto
fixo basta fazer o shift de 16 bits para direita.

    A  mesma  regra   pode   ser   usada   para   transformao   de
ponto-flutuante para ponto-fixo, s que no usaremos shifting e  sim
multiplicaremos  explicitamente  por 65536.0!  Suponha que queiramos
transforma o nmero PI em ponto-fixo:

 +------------------------------------------------------------------+
   FixP = FloatP * 65536.0                                         
                                                                   
   FixP = 3.1415... * 65536.0 = 205887.4161                        
   FixP = 205887                                                   
                                                                   
   FixP = 0003.2439h                                               
 +------------------------------------------------------------------+

    O que nos d  uma  boa  aproximao (se transformarmos 32439h em
ponto flutuante novamente obteremos 3.14149475...).  Apenas a  parte
inteira  do  resultado  (205887.4161) nos interessa.  (205887).  Mas
apareceu um pequenino problema que talvez vc no tenha notado...

    Suponha que o  resultado  da  multiplicao  por  65536.0  desse
205887.865  (por  exemplo,  t?!).  Esse nmero est mais prximo de
205888 do que de 205887!  Se tomarmos apenas a componente inteira do
resultado obteremos um erro ainda  maior  (ponto-fixo  no    muito
preciso, como vc pode notar  pelo  exemplo acima!).  Como fazer para
obter sempre a componente inteira mais  aproximada?!   A  soluo  
somar 0.5 ao resultado da multiplicao por 65536.0!

    Se a componente fracionria for maior ou igual  a  0.5  ento  a
soma  da  componente  fracionria com 0.5 dar valor menor que 2.0 e
maior ou igual a 1.0 (ou  seja, a componente inteira dessa soma ser
sempre 1.0).  Ao contrrio, se a componente fracionria do resultado
da multiplicao por 65536.0 for menor que 0.5  ento  a  componente
inteira  da  soma  dessa componente por 0.5 ser sempre 0.0!  Ento,
somando  o  resultado  da  multiplicao  com  0.5  podemos  ou  no
incrementar a componente  inteira  de  acordo  com  a proximidade do
nmero real com o inteiro mais prximo!

    Se a aproximao no for feita, o erro gira em torno  de  15e-6,
ou seja: 0.000015 (erro a patir da quinta casa decimal!).

    A transformao de um  nmero de ponto-flutuante para ponto-fixo
fica ento:

 +------------------------------------------------------------------+
   FixP = (FloatP * 65536.0) + 0.5                                 
                                                                   
   FixP = (3.1415... * 65536.0) + 0.5 = 205887.4161 + 0.5          
   FixP = 205887.9161                                              
   FixP = 205887  (ignorando a parte fracionria!)                 
                                                                   
   FixP = 0003.2439h                                               
 +------------------------------------------------------------------+

    A transformao contrria (de ponto-fixo para ponto-flutuante) 
menos  traumtica, basta dividir o nmero de ponto fixo por 65536.0.
Eis algumas macros, em C, para as transformaes:

 +-----------------------------------------------------------------+
   #define INT2FIXED(x)    ((long)(x) << 16)                      
   #define FIXED2INT(x)    ((x) >> 16)                            
   #define DOUBLE2FIXED(x) (long)(((x) * 65536.0) + 0.5)          
   #define FIXED2DOUBLE(x) ((double)(x) / 65536.0)                
 +-----------------------------------------------------------------+

    Aritimtica de  ponto-fixo    recomendvel  apenas  no  caso de
requerimento de velocidade e quando no necessitamos de preciso nos
calculos.  O menor nmero  que  podemos  armazenar  na  configurao
atual     1.5259e-5   (1/65536)   e   o   maior     32767.99998,
aproximadamente.  Nmeros maiores  ou  menores  que  esses  no  so
representveis.   Se  o seu programa pode extrapolar esta faixa, no
use   ponto-fixo,   vc   obter   muitos   erros   de   preciso  e,
ocasionalmente, talvez at um erro de "Division By Zero".

    Ateno...   A  implementao dos procedimentos (PROC) acima so
um pouquinho diferentes para mixagem de cdigo...  Os compiladores C
e PASCAL atuais utilizam o par DX:AX para retornar um DWORD,  assim,
no fim de cada PROC e antes do retorno coloque:

 +-----------------------------------------------------------------+
   shld    edx,eax,16                                             
   shr     eax,16                                                 
 +-----------------------------------------------------------------+

    Ou faa melhor ainda: modifique os cdigos!

    Eis a minha implementao  para  as  rotinas FixedMul e FixedDiv
para mixagem de cdigo com C ou TURBO PASCAL:

 +-----------------------------------------------------------------+
   /*                                                             
   ** Arquivo de cabealho FIXED.H                                
   */                                                             
   #if !defined(__FIXED_H__)                                      
   #define __FIXED_T__                                            
                                                                  
   /* Tipagem */                                                  
   typedef long    fixed_t;                                       
                                                                  
   /* Macros de converso */                                      
   #define INT2FIXED(x)    ((fixed_t)(x) << 16)                   
   #define FIXED2INT(x)    ((int)((x) >> 16))                     
   #define DOUBLE2FIXED(x) ((fixed_t)(((x) * 65536.0) + 0.5))     
   #define FIXED2DOUBLE(x) ((double)(x) / 65536.0)                
                                                                  
   /* Declarao das funes */                                   
   fixed_t pascal FixedMul(fixed_t, fixed_t);                     
   fixed_t pascal FixedDiv(fixed_t, fixed_t);                     
                                                                  
   #endif                                                         
 +-----------------------------------------------------------------+
 +-----------------------------------------------------------------+
   {*** Unit FixedPt para TURBO PASCAL ***}                       
   UNIT FIXEDPT;                                                  
                                                                  
   {} INTERFACE {}                                                
                                                                  
   {*** Tipagem ***}                                              
   TYPE                                                           
       TFixed  = LongInt;                                         
                                                                  
   {*** Declarao das funes ***}                               
   FUNCTION FixedMul(M1, M2 : TFixed) : TFixed;                   
   FUNCTION FixedDiv(D1, D2 : TFixed) : TFixed;                   
                                                                  
   {} IMPLEMENTATION {}                                           
                                                                  
   {*** Inclui o arquivo .OBJ compilado do cdigo abaixo ***}     
   {$L FIXED.OBJ}                                                 
                                                                  
   {*** Declara funes como externas ***}                        
   FUNCTION FixedMul(M1, M2 : TFixed) : TFixed; EXTERN;           
   FUNCTION FixedDiv(D1, D2 : TFixed) : TFixed; EXTERN;           
                                                                  
   {*** Fim da Unit... sem inicializaes! ***}                   
   END.                                                           
 +-----------------------------------------------------------------+
 +-----------------------------------------------------------------+
   ; FIXED.ASM                                                    
   ; Mdulo ASM das rotinas de multiplicao e diviso em         
   ; ponto fixo.                                                  
                                                                  
   ; Modelamento de memria e modo do compilador.                 
   IDEAL                                                          
   MODEL LARGE,PASCAL                                             
   LOCALS                                                         
   JUMPS                                                          
   P386        ; Habilita instrues do 386                       
                                                                  
   ; Declara os procedimentos como pblicos                       
   GLOBAL FixedMul : PROC                                         
   GLOBAL FixedDiv : PROC                                         
                                                                  
   ; Inicio do segmento de cdigo.                                
   CODESEG                                                        
                                                                  
   PROC    FixedMul                                               
   ARG     m1:DWORD, m2:DWORD                                     
                                                                  
       mov     eax,[m1]                                           
       mov     ebx,[m2]                                           
       imul    ebx                                                
       shr     eax,16  ; Coloca parte fracionria em AX.          
                       ; DX j contm parte inteira!              
       ret                                                        
                                                                  
   ENDP                                                           
                                                                  
   ; Diviso em ponto fixo.                                       
   ; d1 = Dividendo, d2 = Divisor                                 
   PROC    FixedDiv                                               
   ARG     d1:DWORD, d2:DWORD                                     
                                                                  
       sub     cl,cl       ; CL = flag                            
                           ; == 0 -> resultado positivo.          
                           ; != 0 -> resultado negativo.          
                                                                  
       mov     eax,[d1]    ; pega dividendo                       
                                                                  
       or      eax,eax     ;  negativo?!                         
       jns     @@no_chs1   ; no! ento no troca sinal!          
                                                                  
       neg     eax         ; ! ento troca o sinal e...          
       inc     cl          ; incrementa flag.                     
   @@no_chs1:                                                     
                                                                  
       mov     ebx,[d2]    ; pega divisor                         
                                                                  
       or      ebx,ebx     ;  negativo?!                         
       jns     @@no_chs2   ; no! ento no troca sinal!          
                                                                  
       neg     ebx         ; ! ento troca sinal e...            
       dec     cl          ; decrementa flag.                     
   @@no_chs2:                                                     
                                                                  
       sub     edx,edx     ; Prepara para diviso.                
       shld    edx,eax,16                                         
       shl     eax,16                                             
                                                                  
       div     ebx         ; diviso de valores positivos...      
                           ; ... no precisamos de idiv!          
                                                                  
       or      cl,cl       ; flag == 0?                           
       jz      @@no_chs3   ; sim! resultado  positivo.           
                                                                  
       neg     eax         ; no! resultado  negativo...         
                           ; ... troca de sinal!                  
   @@no_chs3:                                                     
                                                                  
       ;                                                          
       ; Apenas adequa para o compilador                          
       ;                                                          
       shld    edx,eax,16  ; DX:AX contm o DWORD                 
       shr     eax,16                                             
                                                                  
       ret                                                        
                                                                  
   ENDP                                                           
                                                                  
   END                                                            
 +-----------------------------------------------------------------+

