741 lines
25 KiB
Org Mode
741 lines
25 KiB
Org Mode
#+title: 9 -- Conversão de representações de ponto flutuante
|
||
#+author: Blau Araujo
|
||
#+email: cursos@blauaraujo.com
|
||
|
||
#+options: toc:3
|
||
|
||
* Objetivos
|
||
|
||
- Conhecer as formas de codificação binária de números de ponto flutuante.
|
||
- Identificar as representações de números de ponto flutuante com strings.
|
||
- Implementar funções e sub-rotinas para converter os representações de
|
||
ponto flutuante nos valores representados.
|
||
|
||
* Tipos de ponto flutuante em C
|
||
|
||
A linguagem C define três tipos principais de ponto flutuante (x86_64):
|
||
|
||
| Tipo | Precisão | Tamanho |
|
||
|-------------+-----------+----------|
|
||
| =float= | Simples | 4 bytes |
|
||
| =double= | Dupla | 8 bytes |
|
||
| =long double= | Estendida | 16 bytes |
|
||
|
||
A precisão refere-se à quantidade de dígitos que podem ser representados de
|
||
forma exata em um número, sendo um parâmetro essencial para determinar a
|
||
precisão de resultados de operações aritméticas, ate onde podemos representar
|
||
números muito pequenos sem que eles virem zero e distinguir valores em
|
||
operações de comparação.
|
||
|
||
No entanto, observe o que acontece com a execução do exemplo abaixo...
|
||
|
||
Arquivo: [[exemplos/10/precision.c][precision.c]]
|
||
|
||
#+begin_src c :tangle exemplos/10/precision.c
|
||
#include <stdio.h>
|
||
#include <float.h> // Para as precisões
|
||
|
||
int main(void) {
|
||
float f = 10.0f / 3.0f;
|
||
double d = 10.0 / 3.0;
|
||
long double ld = 10.0L / 3.0L;
|
||
|
||
printf("float (%d): %.20f\n", FLT_DIG, f);
|
||
printf("double (%d): %.20lf\n", DBL_DIG, d);
|
||
printf("long double (%d): %.20Lf\n", LDBL_DIG, ld);
|
||
|
||
return 0;
|
||
}
|
||
#+end_src
|
||
|
||
Aqui, nós queremos imprimir os resultados das divisões utilizando os três
|
||
tipos de ponto flutuante e, segundo as definições da =glibc=, quantos dos seus
|
||
dígitos decimais teriam precisão garantida. Para isso, estamos utilizando as
|
||
constantes simbólicas do cabeçalho =float.h=:
|
||
|
||
- =FLT_DIG=: Quantidade de dígitos decimais garantidos no tipo =float=.
|
||
- =DBL_DIG=: Quantidade de dígitos decimais garantidos no tipo =double=.
|
||
- =LDBL_DIG=: Quantidade de dígitos decimais garantidos no tipo =long double=.
|
||
|
||
Compilando e executando:
|
||
|
||
#+begin_example
|
||
:~$ gcc -Wall precision.c
|
||
:~$ ./a.out
|
||
float (6): 3.33333325386047363281
|
||
double (15): 3.33333333333333348136
|
||
long double (18): 3.33333333333333333326
|
||
#+end_example
|
||
|
||
De fato, os resultados são precisos até os dígitos decimais esperados:
|
||
|
||
#+begin_example
|
||
6
|
||
|
|
||
float (6): 3.33333325386047363281
|
||
15
|
||
|
|
||
double (15): 3.33333333333333348136
|
||
18
|
||
|
|
||
long double (18): 3.33333333333333333326
|
||
#+end_example
|
||
|
||
* Representações em expressões constantes
|
||
|
||
Ainda no código do exemplo, observe como os valores de ponto flutuante foram
|
||
escritos:
|
||
|
||
#+begin_src c
|
||
float f = 10.0f / 3.0f;
|
||
double d = 10.0 / 3.0;
|
||
long double ld = 10.0L / 3.0L;
|
||
#+end_src
|
||
|
||
Tivemos que escrever assim porque, por padrão, expressões constantes com ponto
|
||
decimal são avaliadas como =double=, a menos que sejam seguidas de =f= (=float=) ou
|
||
=L= (=long double=), como podemos ver nesta tabela, que inclui as notações com
|
||
expoente:
|
||
|
||
| Literal | Tipo |
|
||
|------------------+-------------|
|
||
| =3.14= | =double= |
|
||
| =3.14f= ou =3.14F= | =float= |
|
||
| =3.14l= ou =3.14L= | =long double= |
|
||
| =3e[+/-]2= | =double= |
|
||
| =3e[+/-]2f= (ou =F=) | =float= |
|
||
| =3e[+/-]2l= (ou =L=) | =long double= |
|
||
|
||
#+begin_quote
|
||
Também existem as representações com expoentes binários, introduzidos no C99
|
||
para representar valores em hexadecimal com expoente em base 2 (ex.: =0x1.8p1=),
|
||
mas eles não serão estudados neste curso.
|
||
#+end_quote
|
||
|
||
Para os nossos propósitos, porém, a representação literal da linguagem C que
|
||
nos interessa é a do tipo =double= (=3.14= ou =0.42=) com ou sem sinal. Sobre isso,
|
||
valores negativos são representados da mesma forma que os positivos, mas com
|
||
o bit de sinal ativado.
|
||
|
||
Sendo assim, os dígitos dos valores convertidos pelas nossas implementações
|
||
deverão corresponder a todos os caracteres da string numérica e serão
|
||
retornados como =double=, o que implica:
|
||
|
||
- Valor retornado em 8 bytes (64 bits) com ou sem sinal;
|
||
- Limite máximo em DBL_MAX
|
||
- Limite mínimo em DBL_TRUE_MIN (menor subnormal).
|
||
|
||
* Codificação binária do tipo double (64 bits)
|
||
|
||
Um número de ponto flutuante com dupla precisão (/double/) ocupa 64 bits
|
||
divididos em três campos:
|
||
|
||
#+begin_example
|
||
63 62 51 0
|
||
┌───┬──────────┬────────────────────────────┐
|
||
│ S │ EXPOENTE │ MANTISSA │
|
||
└───┴──────────┴────────────────────────────┘
|
||
#+end_example
|
||
|
||
De modo a representar números da faixa normal (parte inteira ~>=1~) como:
|
||
|
||
#+begin_example
|
||
(-1)^sinal × (1 + (mantissa/2^52)) × 2^(expoente - 1023)
|
||
#+end_example
|
||
|
||
#+begin_quote
|
||
*Nota:* /mantissa/ é como chamamos a parte fracionária do número.
|
||
#+end_quote
|
||
|
||
** Bits de expoente
|
||
|
||
O expoente diz quantas vezes o valor da mantissa será multiplicado ou
|
||
dividido por 2. O campo possui 11 bits (bits 62 a 52) e pode receber valores
|
||
entre 0 e 2047 que assumirão três significados diferentes:
|
||
|
||
| Expoente | Significado |
|
||
|----------+-----------------------------------------|
|
||
| =0= | Número muito pequenos (subnormais) ou =0= |
|
||
| =2047= | Infinito ou não é um número (=NaN=) |
|
||
| =1= a =2046= | Números na faixa normal |
|
||
|
||
Se o expoente for =0=, ele será interpretado com o valor fixo =-1022= e a
|
||
interpretação será feita sem o bit implícito, resultando na expressão
|
||
de valores iniciados com =0.= alguma coisa. Neste caso, nós teremos valores
|
||
muito pequenos, abaixo dos valores normais e, por isso mesmo, chamados de
|
||
/subnormais/.
|
||
|
||
*Exemplos:*
|
||
|
||
#+begin_example
|
||
0x0000000000000000 => +0.0
|
||
0x0000000000000001 => Menor subnormal positivo
|
||
#+end_example
|
||
|
||
Já a interpretação do expoente =2047= depende do que está na mantissa: se for
|
||
=0=, o número é interpretado como infinito; caso contrário, não será um
|
||
número.
|
||
|
||
*Exemplos:*
|
||
|
||
#+begin_example
|
||
0x7ff0000000000000 => +Infinito (0x7ff = 2047)
|
||
0x7ff0000000000001 => NaN
|
||
#+end_example
|
||
|
||
Quando o expoente representa números na faixa normal, a interpretação dos
|
||
64 bits deve ser feita considerando como se existisse um bit 1 antes da
|
||
mantissa, garantindo que a parte inteira do número normalizado nunca seja
|
||
zero.
|
||
|
||
*Exemplo:*
|
||
|
||
#+begin_example
|
||
123.456 => 0x405edd2f1a9fbe77
|
||
|
||
Primeiros 12 bytes:
|
||
0x405 = 0100 0000 0101
|
||
|
||
bit de sinal (+1)
|
||
↓
|
||
0 10000000101
|
||
↑
|
||
11 bits do expoente (0x405 => 1029)
|
||
|
||
Expoente real:
|
||
1029 - 1023 = 6 => 2^6 = 64
|
||
|
||
Mantissa:
|
||
0xedd2f1a9fbe77
|
||
|
||
Mantissa real:
|
||
1 + 0xedd2f1a9fbe77 / 2^52 = 1.929
|
||
|
||
Valor final:
|
||
+1 × 1.929 × 64 = +123.456
|
||
#+end_example
|
||
|
||
* Codificação binária do tipo float (32 bits)
|
||
|
||
O tipo /float/ armazena números reais com precisão simples em 32 bits, também
|
||
divididos em três campos:
|
||
|
||
#+begin_example
|
||
31 30 22 0
|
||
┌───┬──────────┬────────────────────────────┐
|
||
│ S │ EXPOENTE │ MANTISSA │
|
||
└───┴──────────┴────────────────────────────┘
|
||
#+end_example
|
||
|
||
Deste modo, valores normalizados (parte inteira ~>=1~) podem ser calculados
|
||
pela fórmula:
|
||
|
||
#+begin_example
|
||
(-1)^sinal × (1 + (mantissa/2^23)) × 2^(expoente - 127)
|
||
#+end_example
|
||
|
||
** Bits de expoente
|
||
|
||
O expoente é expresso com 8 bits, o que pode resultar em valores de 0 a 255
|
||
com os seguintes significados:
|
||
|
||
| Expoente | Significado |
|
||
|----------+-----------------------------------|
|
||
| =0= | Número subnormal (=0.xxx=) ou zero. |
|
||
| =1= a =254= | Números na faixa normal (=1.xxx=). |
|
||
| =255= | Infinito ou NaN. |
|
||
|
||
Assim como acontece na representação binária do tipo /double/, expoentes entre
|
||
=1= e =254= implicam a soma de 1 à mantissa real (=mantissa/2^23=) na interpretação
|
||
do valor representado. Com expoente =0=, a mantissa real ainda é utilizada para
|
||
obter a parte fracionária de um número subnormal (=0.xxx=). Contudo, se o
|
||
expoente for =255=, utiliza-se a mantissa armazenada para decidir se o valor é
|
||
infinito (mantissa =0=) ou não é um número (mantissa diferente de =0=).
|
||
|
||
* Unidades de ponto flutuante em x86_64
|
||
|
||
A arquitetura x86_64 representa e manipula números reais por meio de três
|
||
mecanismos históricos:
|
||
|
||
- FPU (/Float-Point/ Unit): coprocessador matemático;
|
||
- Instruções SSE/SSE2: suporte a operações com ponto flutuante;
|
||
- Registradores XMM: utilizados com instruções SSE.
|
||
|
||
A FPU (x87) surgiu em 1980 com o coprocessador 8087, mais tarde integrado ao
|
||
processador 486DX. Ainda é suportado na arquitetura x86_64 por compatibilidade,
|
||
mas raramente é utilizado em códigos modernos -- a não ser para lidar com
|
||
=long double= (números de ponto flutuante com 128 bits).
|
||
|
||
As instruções SSE/SSE2 foram introduzidas nos processadores Pentium III e IV,
|
||
dos anos 2000, como mecanismos preferenciais para operações de ponto flutuante.
|
||
A partir da arquitetura x86_64, as instruções SSE2 tronam-se obrigatórias e
|
||
substituem praticamente toda a FPU em compiladores modernos.
|
||
|
||
Mais recentemente, os processadores passaram a oferecer extensões como AVX
|
||
(Advanced Vector Extensions), que ampliam o número e a largura de registradores
|
||
XMM (passando a YMM e ZMM, com 256 e 512 bits) e otimizam operações vetoriais
|
||
e de ponto flutuante em larga escala. Essas instruções são usadas por
|
||
compiladores modernos em programas de alto desempenho.
|
||
|
||
#+begin_quote
|
||
*Nota:* os registradores de propósito geral nunca foram projetados para trabalhar
|
||
com ponto flutuante. Mas, ao utilizá-los nas nossas demonstrações de conversões,
|
||
nossos objetivos são apresentar o núcleo lógico dessas operações e demonstrar
|
||
os problemas e as limitações que os recursos modernos superam.
|
||
#+end_quote
|
||
|
||
* Conversão de strings para float
|
||
|
||
Ao converter uma string como ="12.75"= para um número real no Assembly, as
|
||
principais abordagens para representar o resultado são:
|
||
|
||
- Formato /escalado/ (também chamado de /fixed-point/);
|
||
- Formato empacotado como número IEEE 754 de 32 bits (/float/).
|
||
|
||
Ambas as abordagens são úteis em contextos distintos e possuem vantagens e
|
||
limitações que devem ser compreendidas e consideradas.
|
||
|
||
** Alternativa 1: Representação escalada (fixed-point)
|
||
|
||
Em Assembly (especialmente sem FPU), não há suporte direto para números com
|
||
parte fracionária. Mas é possível simular valores reais utilizando inteiros,
|
||
contanto que todos os números sejam representados na mesma /escala/. A ideia é
|
||
preservar as casas decimais multiplicando o valor original por um fator
|
||
fixo (como =2^23=) e armazenar o resultado como um inteiro.
|
||
|
||
Por exemplo, podemos representar o número =12.34= como:
|
||
|
||
#+begin_example
|
||
12.75 × 2^23 = 1065353216
|
||
#+end_example
|
||
|
||
#+begin_quote
|
||
O exemplo de fator =2^23= (ou =8.388.608=) está relacionado com o fato deste ser
|
||
o número de bits disponíveis para representar a mantissa em um número /float/
|
||
de 32 bits no padrão IEEE 754, como vimos.
|
||
#+end_quote
|
||
|
||
O valor em escala pode ser armazenado em um registrador de propósito geral e
|
||
operado com instruções inteiras comuns, como =add=, =sub=, =imul=, =idiv=, etc. Após
|
||
a operação, o resultado pode ser interpretado diretamente em escala ou
|
||
/desescalado/ para a forma de ponto flutuante.
|
||
|
||
Por exemplo, numa conversão de Celsius para Fahrenheit...
|
||
|
||
#+begin_src asm
|
||
; ----------------------------------------------------------
|
||
; RAX = Temperatura em Celsius × 2^23
|
||
; Conversão para Fahrenheit: F = C × 9/5 + 32
|
||
; ----------------------------------------------------------
|
||
imul rax, rax, 9 ; rax = rax × 9
|
||
cqo ; estende o sinal de rax em rdx (para imul)
|
||
mov rcx, 5 ; divisor
|
||
idiv rcx ; rax = rax / 5
|
||
add rax, 32 << 23 ; rax = rax + (32 × 2^23)
|
||
|
||
; Resultado final: Fahrenheit × 2^23
|
||
#+end_src
|
||
|
||
Para exibir o resultado como uma string, bastaria dividi-lo por =2^23=
|
||
(=8.388.608=). Em Assembly, podemos utilizar =mov= e =idiv= para obter parte
|
||
inteira e parte decimal separadas antes de fazer a conversão para texto...
|
||
|
||
#+begin_src asm
|
||
; ----------------------------------------------------------
|
||
; RAX = Fahrenheit × 2^23
|
||
; ----------------------------------------------------------
|
||
mov rbx, rax ; copia valor escalado
|
||
mov rcx, 8388608 ; fator da escala (2^23)
|
||
xor rdx, rdx ; zera rdx para o resto
|
||
idiv rcx ; rax = parte inteira, rdx = parte fracionária (resto)
|
||
; converter a partte inteira para string...
|
||
; ----------------------------------------------------------
|
||
; A parte fracionária ainda está em escala...
|
||
; ----------------------------------------------------------
|
||
mov rbx, 1000 ; multiplicador da parte fracionária
|
||
imul rdx, rbx ; x1000 => um inteiro desescalado com 3 algarismos
|
||
idiv rcx ; rax = parte fracionária
|
||
; converter a parte fracionária para string e concatenar com '.'...
|
||
#+end_src
|
||
|
||
A grande vantagem dessa abordagem é sua simplicidade e compatibilidade com
|
||
qualquer conjunto de instruções, sem depender da FPU ou de registradores XMM.
|
||
Contudo, ela exige que todas as operações preservem a escala, o que requer
|
||
muita atenção com as operações aritméticas. Ao final, o resultado pode ser
|
||
convertido para exibição ou empacotado como /float/ para ser usado com
|
||
instruções de ponto flutuante ou passado para funções externas da Glibc.
|
||
|
||
** Alternativa 2: Representação IEEE 754
|
||
|
||
A representação empacotada como float segue o padrão IEEE 754. Um número como
|
||
=12.75=, por exemplo, seria convertido para a forma binária através dos
|
||
seguintes passos:
|
||
|
||
- Determinar o sinal (0 para positivo);
|
||
- Converter o número para binário normalizado;
|
||
- Calcular o expoente com o bias (deslocamento);
|
||
- Codificar a mantissa (sem o bit implícito).
|
||
|
||
*Passo 1: determinar o sinal*
|
||
|
||
O número é positivo, então o sinal é =0=.
|
||
|
||
*Passo 2: converter o número para binário normalizado*
|
||
|
||
- Parte inteira (12): =11000= em base 2.
|
||
- Parte fracionária (0.75): =11=, obtido por multiplicações sucessivas por 2.
|
||
- Resultado da conversão: =1100.11=
|
||
|
||
Normalizar um binário de ponto flutuante é representá-lo como:
|
||
|
||
#+begin_example
|
||
1.xxxxxx × 2^n
|
||
#+end_example
|
||
|
||
No caso de =1100.11=, será preciso deslocar o ponto 3 posições para a esquerda.
|
||
Portanto, para manter seu valor original, nós temos que multiplicá-lo por
|
||
=2^3=:
|
||
|
||
#+begin_example
|
||
1100.11 = 1.10011 × 2^3
|
||
#+end_example
|
||
|
||
*Passo 3: calcular o expoente com bias*
|
||
|
||
Como o deslocamento da normalização foi de =2^3=, o expoente com /bias/ será:
|
||
|
||
#+begin_example
|
||
3 + 127 (bias IEEE 754) = 130
|
||
130 = 10000010 na base 2
|
||
#+end_example
|
||
|
||
*Passo 4: codificar a mantissa*
|
||
|
||
A mantissa é a parte fracionária do valor normalizado em binário seguida de
|
||
tantos zeros quanto forem necessários para totalizar 23 bits, portanto...
|
||
|
||
#+begin_example
|
||
Mantissa de 1.10011 => 10011
|
||
Mantissa armazenada => 10011000000000000000000
|
||
#+end_example
|
||
|
||
*Empacotamento dos campos*
|
||
|
||
#+begin_example
|
||
Sinal : 0 (positivo)
|
||
Expoente : 10000010 (130 = 127 + 3)
|
||
Mantissa : 10011000000000000000000
|
||
Resultado: 01000001010011000000000000000000 => 0x41480000 em hexa
|
||
#+end_example
|
||
|
||
A forma empacotada é utilizada em operações de ponto flutuante com SSE/FPU
|
||
e para passar argumentos =float= para funções em C. Ela permite o uso de
|
||
instruções especializadas da arquitetura, mas requer decodificação
|
||
e empacotamento.
|
||
|
||
** Quando optar por uma ou outra alternativa
|
||
|
||
Via de regra, nós só empacotamos para o formato IEEE 754 quando realmente
|
||
precisamos interagir com outros componentes que exijam o tipo /float/. Nas
|
||
situações mais comuns, a representação em escala é mais que suficiente.
|
||
|
||
| Objetivo | Formato recomendado |
|
||
|---------------------------------+---------------------|
|
||
| Aritmética simples com inteiros | Escalado (/fixed/) |
|
||
| Comparações e fórmulas básicas | Escalado (/fixed/) |
|
||
| Compatibilidade com SSE/FPU | IEEE 754 |
|
||
| Chamada de função com "%f" | IEEE 754 |
|
||
|
||
** Dificuldades comuns
|
||
|
||
*** 1. Arredondamento de frações
|
||
|
||
Ao representar "0.333" como inteiro escalado, há perda de precisão. Aumentar o
|
||
fator de escala (por exemplo, 2^30 em vez de 2^23) melhora a precisão, mas pode
|
||
causar /overflow/ em multiplicações.
|
||
|
||
*** 2. Erros ao interpretar IEEE como inteiro
|
||
|
||
Se usarmos um /float/ empacotado (como =0x41200000=, que representa =10.0=) em uma
|
||
operação inteira, o valor será incorreto. Isso ocorre porque o padrão IEEE é
|
||
uma codificação binária, e não o valor numérico diretamente.
|
||
|
||
*** 3. Falta de suporte a ponto flutuante
|
||
|
||
Alguns ambientes minimalistas, como bootloaders ou sistemas bare-metal,
|
||
podem não ter suporte a SSE/FPU. Nestes casos, a representação escalada é
|
||
a única alternativa viável.
|
||
|
||
** Diferenças na conversão para double (64 bits)
|
||
|
||
O tipo /double/ (também do padrão IEEE 754) ocupa 64 bits e permite maior
|
||
precisão e maior faixa de representação. Sua estrutura é similar à de um
|
||
/float/, mas com:
|
||
|
||
- 1 bit para o sinal
|
||
- 11 bits para o expoente (~bias=1023~)
|
||
- 52 bits para a mantissa (com bit 1 implícito)
|
||
|
||
Sendo assim, as operações de empacotamento e desempacotamento seguem o mesmo
|
||
processo do /float/, mas precisam trabalhar com registradores de 64 bits
|
||
completos.
|
||
|
||
A representação escalada também pode ser estendida para o caso de valores que
|
||
eventualmente serão convertidos para /double/, com os mesmos benefícios de
|
||
usar apenas instruções inteiras durante o processamento. Contudo, quando
|
||
demonstramos o uso do tipo /float/, nós adotamos o fator de escala =2^23=,
|
||
por ser compatível com o número de bits disponíveis para a mantissa. No caso
|
||
de /double/, porém, a mantissa tem 52 bits, o que permitiria escalar até =2^52=
|
||
(aproximadamente 4.5 × 10^15) sem perda de precisão por arredondamento.
|
||
|
||
No entanto, escalar por =2^52= pode causar overflow em algumas multiplicações
|
||
se o valor base for muito grande. Assim, uma alternativa prática seria
|
||
escalar por =2^30=, =2^32= ou outro fator suficientemente alto, mas ainda seguro
|
||
para uso com registradores de 64 bits.
|
||
|
||
** Sub-rotinas para demonstração
|
||
|
||
Aqui estão duas sub-rotinas:
|
||
|
||
- =str_to_float_scaled=: converte strings no formato =<int>.<frac>= para
|
||
representações de números de ponto flutuante como inteiros de 32 bits
|
||
escalados.
|
||
|
||
- =str_to_float_ieee754=: converte strings no formato =<int>.<frac>= para
|
||
representações de números de ponto empacotados no formato IEEE 754 de
|
||
32 bits.
|
||
|
||
Embora seja possível que elas funcionem corretamente, nenhuma delas chegou
|
||
a ser testada. Portanto, o exercício proposto para esta aula será:
|
||
|
||
- Analisar o código de cada uma delas;
|
||
- Compreender e explicar as decisões tomadas;
|
||
- Estudar as instruções ainda desconhecidas;
|
||
- Elaborar formas de testá-las;
|
||
- Corrigir eventuais erros;
|
||
- Buscar formas de otimizá-las;
|
||
- Implementar versões para 64 bits (/double/).
|
||
|
||
*** =str_to_float_scaled=
|
||
|
||
#+begin_src asm
|
||
; ----------------------------------------------------------
|
||
; Converte uma string no formato <int>.<uint> para float
|
||
; Entrada: RDI -> ponteiro para string válida
|
||
; Saída: EAX = número codificado como float (IEEE 754, 32 bits)
|
||
; Altera: RBX, RCX, RDX, RSI, R8, R9, R10
|
||
; ----------------------------------------------------------
|
||
str_to_float_scaled:
|
||
; ----------------------------------------------------------
|
||
push rbx ; salva rbx na pilha
|
||
push rsi ; salva rsi na pilha
|
||
mov rsi, rdi ; salva ponteiro da string em rsi
|
||
xor rbx, rbx ; RBX = parte inteira
|
||
xor rcx, rcx ; RCX = parte fracionária
|
||
xor rdx, rdx ; RDX = divisor decimal (potência de 10)
|
||
xor r8d, r8d ; sinal (0 = positivo, 1 = negativo)
|
||
; --------------------------------------------------
|
||
; Verifica e armazena o sinal
|
||
; --------------------------------------------------
|
||
mov al, byte [rsi]
|
||
cmp al, '-'
|
||
jne .check_plus
|
||
inc rsi
|
||
mov r8b, 1
|
||
jmp .read_integer
|
||
.check_plus:
|
||
cmp al, '+'
|
||
jne .read_integer
|
||
inc rsi
|
||
; --------------------------------------------------
|
||
; Lê parte inteira: <int>
|
||
; --------------------------------------------------
|
||
.read_integer:
|
||
xor eax, eax ; eax = 0
|
||
.read_int_loop:
|
||
mov al, byte [rsi]
|
||
cmp al, 0 ; fim da string
|
||
je .build_float
|
||
cmp al, '.' ; fim da parte inteira
|
||
je .read_fraction
|
||
sub al, '0' ; converte dígito em número
|
||
jb .build_float ; se al < 0x30, fim do número
|
||
cmp al, 9
|
||
ja .build_float ; se al > 9, fim do número
|
||
imul rbx, rbx, 10 ; rbx = rbx * 10
|
||
add bl, al ; bl = bl + al
|
||
inc rsi
|
||
jmp .read_int_loop
|
||
; --------------------------------------------------
|
||
; Lê parte decimal: .<uint>
|
||
; --------------------------------------------------
|
||
.read_fraction:
|
||
inc rsi ; pula o ponto
|
||
xor ecx, ecx ; zera a parte decimal
|
||
mov edx, 1 ; divisor = 1
|
||
.read_frac_loop:
|
||
mov al, byte [rsi]
|
||
cmp al, 0 ; número é xxx.0
|
||
je .build_float
|
||
sub al, '0' ; converte dígito em número
|
||
jb .build_float ; se al < 0x30, fim do número
|
||
cmp al, 9
|
||
ja .build_float ; se al > 9, fim do número
|
||
imul rcx, rcx, 10 ; rcx = rcx * 10
|
||
add cl, al ; cl = cl + al
|
||
imul rdx, rdx, 10 ; divisor *= 10
|
||
inc rsi
|
||
jmp .read_frac_loop
|
||
; --------------------------------------------------
|
||
; Combina: float = inteiro + (fracao / divisor)
|
||
; rbx = parte inteira (int)
|
||
; rcx = parte fracionária (uint)
|
||
; rdx = divisor decimal
|
||
; converte parte inteira para float (em eax)
|
||
; --------------------------------------------------
|
||
.build_float:
|
||
mov eax, ebx
|
||
test r8b, r8b
|
||
jz .skip_negate_int ; número é positivo
|
||
neg eax ; aplica sinal de negativo
|
||
; --------------------------------------------------
|
||
; converte parte decimal: (rcx / rdx) → aproximação inteira × escala
|
||
; Aqui usamos multiplicação por 2^n e bit shift para simular uma fração
|
||
; Exemplo: 123 / 1000 ≈ (123 << 23) / 1000
|
||
; --------------------------------------------------
|
||
.skip_negate_int:
|
||
shl rcx, 23
|
||
xor r9d, r9d
|
||
div rdx ; rcx / rdx (com resto em rdx), quociente em rax
|
||
mov r9d, eax ; parte decimal em r9d
|
||
; --------------------------------------------------
|
||
; soma a parte inteira com a parte decimal (ambos em "escala flutuante")
|
||
; --------------------------------------------------
|
||
shl eax, 23 ; inteiro convertido para a mesma escala
|
||
add eax, r9d ; eax = pf aproximado * 2^23
|
||
pop rsi
|
||
pop rbx
|
||
ret
|
||
|
||
#+end_src
|
||
|
||
*** =str_to_float_ieee754=
|
||
|
||
#+begin_src asm
|
||
; ----------------------------------------------------------
|
||
; Converte uma string no formato <int>.<uint> para float IEEE 754 (32 bits)
|
||
; Entrada: RDI -> ponteiro para string válida
|
||
; Saída: EAX = número codificado como float (IEEE 754, 32 bits)
|
||
; Altera: RBX, RCX, RDX, RSI, R8, R9, R10
|
||
; ----------------------------------------------------------
|
||
str_to_float_ieee754:
|
||
; ----------------------------------------------------------
|
||
push rbx
|
||
push rsi
|
||
mov rsi, rdi ; ponteiro da string
|
||
xor rbx, rbx ; parte inteira
|
||
xor rcx, rcx ; parte fracionária
|
||
xor rdx, rdx ; divisor decimal (potência de 10)
|
||
xor r8d, r8d ; sinal (0 = positivo, 1 = negativo)
|
||
; --------------------------------------------------
|
||
; Sinal
|
||
; --------------------------------------------------
|
||
mov al, byte [rsi]
|
||
cmp al, '-'
|
||
jne .check_plus
|
||
inc rsi
|
||
mov r8b, 1
|
||
jmp .read_int
|
||
.check_plus:
|
||
cmp al, '+'
|
||
jne .read_int
|
||
inc rsi
|
||
; --------------------------------------------------
|
||
; Parte inteira
|
||
; --------------------------------------------------
|
||
.read_int:
|
||
xor eax, eax
|
||
.read_int_loop:
|
||
mov al, byte [rsi]
|
||
cmp al, 0
|
||
je .build_scaled
|
||
cmp al, '.'
|
||
je .read_frac
|
||
sub al, '0'
|
||
jb .build_scaled
|
||
cmp al, 9
|
||
ja .build_scaled
|
||
imul rbx, rbx, 10
|
||
add bl, al
|
||
inc rsi
|
||
jmp .read_int_loop
|
||
; --------------------------------------------------
|
||
; Parte fracionária
|
||
; --------------------------------------------------
|
||
.read_frac:
|
||
inc rsi
|
||
xor ecx, ecx
|
||
mov edx, 1
|
||
.read_frac_loop:
|
||
mov al, byte [rsi]
|
||
cmp al, 0
|
||
je .build_scaled
|
||
sub al, '0'
|
||
jb .build_scaled
|
||
cmp al, 9
|
||
ja .build_scaled
|
||
imul rcx, rcx, 10
|
||
add cl, al
|
||
imul rdx, rdx, 10
|
||
inc rsi
|
||
jmp .read_frac_loop
|
||
; --------------------------------------------------
|
||
; Combina: inteiro + (frac/divisor)
|
||
; Resultado com fator 2^23
|
||
; --------------------------------------------------
|
||
.build_scaled:
|
||
mov eax, ebx
|
||
test r8b, r8b
|
||
jz .skip_neg
|
||
neg eax
|
||
.skip_neg:
|
||
shl rcx, 23
|
||
xor r9d, r9d
|
||
div rdx ; rcx / rdx -> rax
|
||
mov r9d, eax ; parte fracionária escalada
|
||
shl eax, 23 ; inteiro * 2^23
|
||
add eax, r9d ; valor total em escala 2^23
|
||
; --------------------------------------------------
|
||
; IEEE 754: empacotamento
|
||
; --------------------------------------------------
|
||
xor ecx, ecx
|
||
test eax, eax
|
||
jns .abs_ready
|
||
neg eax
|
||
mov ecx, 1 ; sinal = 1
|
||
.abs_ready:
|
||
bsr r8d, eax ; MSB
|
||
mov edx, r8d
|
||
sub edx, 23
|
||
add edx, 127 ; bias
|
||
mov ebx, eax
|
||
mov ecx, r8d
|
||
sub ecx, 23
|
||
shl ebx, cl
|
||
and ebx, 0x7FFFFF ; mantissa
|
||
|
||
shl r8d, 31 ; sinal
|
||
shl edx, 23 ; expoente
|
||
or eax, edx
|
||
or eax, ebx
|
||
or eax, r8d
|
||
|
||
pop rsi
|
||
pop rbx
|
||
ret
|
||
#+end_src
|
||
|
||
* Referências
|
||
|
||
- [[https://www-users.cse.umn.edu/~vinals/tspot_files/phys4041/2020/IEEE%20Standard%20754-2019.pdf?utm_source=chatgpt.com][IEEE Standard for Floating-Point Arithmetic]]
|
||
- [[https://en.wikipedia.org/wiki/IEEE_754?utm_source=chatgpt.com][Wikipedia: IEEE 754]]
|