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