Jump to content
Sign in to follow this  
fredericopissarra

Um pequeno código exemplo de conversão de ponto-fixo para ponto-flutuante

Recommended Posts

Artigo original:  https://is.gd/28eIsS

Num outro artigo (este aqui) mostrei como obter o valor representado pela estrutura de um tipo ponto flutuante (float) e vice-versa, mas não mostrei um código fonte de exemplo. Eis aqui um, mas com falhas, porque não leva em consideração valores muito grandes, muito pequenos, subnormais, infinitos e NaNs. Ele converte um valor em ponto fixo (limitado), onde a parte inteira i e a parte fracionária f do valor é fornecida à função. Como está a parte fracionária jamais poderia ser 0.01 ou 0.00034, etc. Apenas valores como: 3.14, 100.4003, etc podem ser fornecidos.

A rotina converte o valor f na componente fracionária e junta as duas numa grande variável de 64 bits, deslocando os bits até isolar o bit de mais alta ordem no bit 32 (o 1.0. implícito num float) e daí obtemos o valor em ponto flutuante. Deixo o código para a análise de vocês:

#include <stdio.h>
#include <assert.h>

// União com a estrutura de um float, para faciliar
// as coisas...
union fp_u {
  float f;

  struct {
    unsigned int m:23;
    unsigned int e:8;
    unsigned int s:1;
  } __attribute__((packed));
};

// Calcula o log, na base 10, de x.
static int log10_( unsigned int x )
{
  int n = 0;

  while ( x >= 10 )
  {
    x /= 10;
    n++;
  }

  return n;
}

// Calcula 10^x (x precisa ser menor que 10).
static unsigned long long pow10_( unsigned int x )
{
  unsigned long long n = 1;

  assert( x < 10 );

  while ( x-- )
    n *= 10;

  return n;
}

// Converte um ponto fixo no formato (i,f) para float.
// Essa rotina tem multiplas falhas:
//  A parte fracionária não permite valores como 0.01, por exemplo.
//  Não verifico por subnormais, nans e infinitos.
//  Não trato valores negativos.
float fixed2float( unsigned int i, unsigned int f )
{
  unsigned int m;
  unsigned long long n;
  int e = 0;
  union fp_u fp = { .f = 0.0 }; // OK... 0.0f é apenas 0x00000000.

  // 0.0 é um caso especial.
  if ( i != 0 && f != 0 )
  {
    // Precisamos transformat a parte fracionária na represetação binária
    // do número fixo. Poderíamos fazer isso com ponto fluautuante, mas o objetivo
    // aqui é evitar ponto flutuante, não é?
    //
    // A mágica aqui é que na representação de número fixo (iiii.ffff) a parte fracionária
    // é (f / 2³²), mas temos apenas f inteiro. Então, coloco f na parte inteira (multiplicando por
    // 2³², e divido pela grandeza do valor (10^(log(f)+1)).
    //
    // Isso garante que m terá apeans 32 bits de tamanho final e todo o cálculo é inteiro (nada de
    // ponto flutuante aqui).
    m = ( ( unsigned long long )f << 32 ) / ( pow10_( log10_( f ) + 1 ) );

    // Agora que temos a representação fracionária correta, basta colocar os dois valores
    // nas posições corretas. A parte inteira são os 32 bits superiores, a parte fracionária, os
    // 32 bits inferiores.
    n = m + ( ( unsigned long long )i << 32 );

    // Um float normalizado sempre tem 1 implícito. Assim, se qualquer um dos 31 bits supeiores for 1, 
    // então temos que fazer deslocamentos para a direita, senão, para a esquerda, até que os 32 bits
    // superiores contenha apenas 1.
    //
    // OBS: ~0ULL << 33 == 0xfffffffe00000000ULL,
    //      ~0ULL << 32 == 0xffffffff00000000ULL e, é claro,
    //       1ULL << 32 = 0x0000000100000000ULL.
    if ( n & ( ~0ULL << 33 ) )
    {
      // Ao deslocar para a direita, incrementa o expoente.
      // OBS: Dá pra melhorar isso contanto a quantidade de 'leading zeros' e deslocando a
      //      quantidade necessária de uma vez só...
      while ( ( n & ( ~0ULL << 32 ) ) != ( 1ULL << 32 ) )
      {
        n >>= 1;
        e++;
      }
    }
    else
    {
      // Ao descolar para a esquerda, decrementa o expoente.
      // OBS: Dá pra melhorar isso contanto a quantidade de 'leading zeros' e deslocando a
      //      quantidade necessária de uma vez só...
      while ( ! ( n & ( 1ULL << 32 ) ) )
      {
        n <<= 1;
        e--;
      }
    }

    fp.m = n >> 9;  // Joga fora os 9 bits inferiores (precisams de apenas 23 bits).
    fp.e = 127+e;   // Calcula o expoente do float.
    fp.s = 0; // não lido com sinal, por enquanto!

    // Acerta o arredondamento. Precisamos arredondar a "mantissa" se o 9º bit for 1.
    if ( n & 0x100 )
      fp.m++;

    // Se a "mantissa" chegou a zero, temos que delocar mais um bit, decrementando o expoente.
    if ( ! fp.m )
      fp.e--;
  }

  return fp.f;
}

// Um pequeno teste.
int main( void )
{
  printf( "%.20f = %.20f\n", 3.1416f, fixed2float( 3, 1416 ) );
}

Compilando e executando:

$ cc -o test test.c
$ ./test
3.14159989356994628906 = 3.14159989356994628906

 

Edited by fredericopissarra

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Sign in to follow this  

  • Recently Browsing   0 members

    No registered users viewing this page.

×
×
  • Create New...