Dica: Converter uma char de 8 bytes em double float em C/C++

Arucard1983

Power Member
Vou partilhar um snipet de código em C/C++ que permite transformar um array char de 8 bytes, como acontece na leitura de ficheiros binários, num número de vírgula flutuante padrão. :o

- Neste código, assume-se que array representa um número no formato big-endian, como acontece em muitos ficheiros de dados binários. (No meu projecto de doutoramento em Física Nuclear, acabei por ter a necessidade de praticar engenharia reversa :joker: aos ficheiros do software LabView, e muitos dos dados representam números de vírgula flutuante no formato hexadecimal... :002:)

- Mas a maioria dos processadores utilizam o formato little-endian, e após experimentar uns cinco métodos que falharam todos de forma gravosa, ou davam NaN, ou davam um número fora do expectável, acabei por implementar um algoritmo, portável, e ainda é endianess-safe :004:!

Código:
double MyUtils::CharToDouble(unsigned char* x)
{
// The IEEE-754 double float number on hexadecimal format requires some delicate tasks, since it will follow the pattern 1-byte*11-byte*52-byte
unsigned int x7,x6,x5,x4,x3,x2,x1,x0,x1e,x1m,exp;
double z,zd,sign,expf;
x0 = x[0];
x1 = x[1];
x2 = x[2];
x3 = x[3];
x4 = x[4];
x5 = x[5];
x6 = x[6];
x7 = x[7];
// Get the number sign, and fix the header
if(x0 >= 128)
{
   x0 = x0 - 128;
   sign = -1.0;
}
else
{
   sign = 1.0;
}
// Split the second byte, since the first half belongs to the exponential, and the second half to the mantissa.
x1e = x1 / 16;
x1m = x1 % 16;
// Evaluate the exponential part. Note: It is necessary to split the integer and fractional conversion to avoid mistakes!
exp = x0*16+x1e;
expf = 1.0*exp - 1023.0;
// Evaluate the mantissa
zd = 1 + x1m*std::pow(2,-4) + x2*std::pow(2,-12) + x3*std::pow(2,-20) + x4*std::pow(2,-28) + x5*std::pow(2,-36) + x6*std::pow(2,-44) + x7*std::pow(2,-52);
// Evaluate the final result:
z = sign * std::pow(2,expf) * zd;
return z; 
}

Para funcionar, devem aplicar a função de leitura preferida (fopen) com 8 bytes, e exportar para um array unsigned char de 9 bytes (por causa do símbolo '\0'), depois apliquem a função conforme descrita.

Com um pequeno aperfeiçoamento, podem meter um segundo argumento para indicar a endianess do argumento, e desta forma obtêm-se um double sem grandes problemas... :D

......................................

Como resolviam este problema ?
 
Código:
#include <iostream>
#include <cstdint>

union uint2float {
  uint32_t u;
  float val;
};

union uint2double {
  uint64_t u;
  double val;
};

int main()
{
  uint2float resf = { 0x41955c29 };
  std::cout << resf.val << std::endl;

  uint2double resd = { 0x4032ab851eb851ec };
  std::cout << resd.val << std::endl;
}

Agora tudo depende de como leres do ficheiro, mas se leres 64 bits seguidos para um uint64_t usando uma bitstream não deverás ter nenhum problema, a não ser que sejam criados por outro sistema.


Trabalhando com ficheiros:
Código:
   double d = 18.7; // 0x 40 32 b3 33 33 33 33 33
   fstream fo;
   fo.open("double.raw", fstream::out | fstream::binary);
   if(!fo.is_open())
     return 1;
   fo.write(reinterpret_cast<char *>(&d), 8);
   fo.close();
 
   fstream fi;
   fi.open("double.raw", fstream::in | fstream::binary);
   if(!fi.is_open())
     return 1;
   fi.read(reinterpret_cast<char *>(&d), 8);
   cout << d << endl;
   fi.close();

Mas se queres ter a possibilidade de mudares o endianess porque estás a trabalhar com ficheiros criados por outro sistema com endianess diferente:

Código:
   double d = 18.7; // 0x 40 32 b3 33 33 33 33 33
   fstream fo;
   fo.open("double.raw", fstream::out | fstream::binary);
   if(!fo.is_open())
     return 1;
   fo.write(reinterpret_cast<char *>(&d), 8);
   fo.close();
 
   fstream fi;
   fi.open("double.raw", fstream::in | fstream::binary);
   if(!fi.is_open())
     return 1;
 
   union double2char
   {
     double d;
     unsigned char s[8];
     void switch_endianess()
     {
       unsigned char aux;
       aux = s[0]; s[0] = s[7]; s[7] = aux;
       aux = s[1]; s[1] = s[6]; s[6] = aux;
       aux = s[2]; s[2] = s[5]; s[5] = aux;
       aux = s[3]; s[3] = s[4]; s[4] = aux;
     }
   };
   typedef union double2char double2char;
   double2char x;
 
   fi.read(reinterpret_cast<char *>(&x.s), 8);
   cout << x.d << endl;
   x.switch_endianess();
   cout << x.d << endl;
   fi.close();
 
Última edição:
Back
Topo