2012-04-07 20 views
6

Estoy tratando de traducir el siguiente código Python en C++:Desembalaje flotadores hexagonal codificada

import struct 
import binascii 


inputstring = ("0000003F" "0000803F" "AD10753F" "00000080") 
num_vals = 4 

for i in range(num_vals): 
    rawhex = inputstring[i*8:(i*8)+8] 

    # <f for little endian float 
    val = struct.unpack("<f", binascii.unhexlify(rawhex))[0] 
    print val 

    # Output: 
    # 0.5 
    # 1.0 
    # 0.957285702229 
    # -0.0 

para que se lea de 32 bits el valor de la cadena hexadecimal-codificado, lo convierte en una matriz de bytes con el método unhexlify, y lo interpreta como un valor flotante little-endian.

Los siguientes casi funciona, pero el código es una especie de mierda (y el último 00000080 analiza incorrectamente):

#include <sstream> 
#include <iostream> 


int main() 
{ 
    // The hex-encoded string, and number of values are loaded from a file. 
    // The num_vals might be wrong, so some basic error checking is needed. 
    std::string inputstring = "0000003F" "0000803F" "AD10753F" "00000080"; 
    int num_vals = 4; 


    std::istringstream ss(inputstring); 

    for(unsigned int i = 0; i < num_vals; ++i) 
    { 
     char rawhex[8]; 

// The ifdef is wrong. It is not the way to detect endianness (it's 
// always defined) 
#ifdef BIG_ENDIAN 
     rawhex[6] = ss.get(); 
     rawhex[7] = ss.get(); 

     rawhex[4] = ss.get(); 
     rawhex[5] = ss.get(); 

     rawhex[2] = ss.get(); 
     rawhex[3] = ss.get(); 

     rawhex[0] = ss.get(); 
     rawhex[1] = ss.get(); 
#else 
     rawhex[0] = ss.get(); 
     rawhex[1] = ss.get(); 

     rawhex[2] = ss.get(); 
     rawhex[3] = ss.get(); 

     rawhex[4] = ss.get(); 
     rawhex[5] = ss.get(); 

     rawhex[6] = ss.get(); 
     rawhex[7] = ss.get(); 
#endif 

     if(ss.good()) 
     { 
      std::stringstream convert; 
      convert << std::hex << rawhex; 
      int32_t val; 
      convert >> val; 

      std::cerr << (*(float*)(&val)) << "\n"; 
     } 
     else 
     { 
      std::ostringstream os; 
      os << "Not enough values in LUT data. Found " << i; 
      os << ". Expected " << num_vals; 
      std::cerr << os.str() << std::endl; 
      throw std::exception(); 
     } 
    } 
} 

(compila en OS X 10.7/gcc-4.2.1, con un simple g++ blah.cpp)

Particularmente, me gustaría deshacerme de las cosas macro BIG_ENDIAN, ya que estoy seguro de que hay una manera más agradable de hacerlo, como se discute en this post.

Algunos otros detalles al azar: no puedo usar Boost (una dependencia demasiado grande para el proyecto). La cadena contendrá normalmente entre 1,536 (8 * 3) y 98304 valores de coma flotante (32 * 3), a lo sumo 786432 (64 * 3)

(Edit2: añadió otro valor, 00000080 == -0.0)

Respuesta

0

Esto es lo que terminamos con, OpenColorIO/src/core/FileFormatIridasLook.cpp

(respuesta de Amardeep con el sin firmar uint32_t solución probablemente trabajar también)

// convert hex ascii to int 
    // return true on success, false on failure 
    bool hexasciitoint(char& ival, char character) 
    { 
     if(character>=48 && character<=57) // [0-9] 
     { 
      ival = static_cast<char>(character-48); 
      return true; 
     } 
     else if(character>=65 && character<=70) // [A-F] 
     { 
      ival = static_cast<char>(10+character-65); 
      return true; 
     } 
     else if(character>=97 && character<=102) // [a-f] 
     { 
      ival = static_cast<char>(10+character-97); 
      return true; 
     } 

     ival = 0; 
     return false; 
    } 

    // convert array of 8 hex ascii to f32 
    // The input hexascii is required to be a little-endian representation 
    // as used in the iridas file format 
    // "AD10753F" -> 0.9572857022285461f on ALL architectures 

    bool hexasciitofloat(float& fval, const char * ascii) 
    { 
     // Convert all ASCII numbers to their numerical representations 
     char asciinums[8]; 
     for(unsigned int i=0; i<8; ++i) 
     { 
      if(!hexasciitoint(asciinums[i], ascii[i])) 
      { 
       return false; 
      } 
     } 

     unsigned char * fvalbytes = reinterpret_cast<unsigned char *>(&fval); 

#if OCIO_LITTLE_ENDIAN 
     // Since incoming values are little endian, and we're on little endian 
     // preserve the byte order 
     fvalbytes[0] = (unsigned char) (asciinums[1] | (asciinums[0] << 4)); 
     fvalbytes[1] = (unsigned char) (asciinums[3] | (asciinums[2] << 4)); 
     fvalbytes[2] = (unsigned char) (asciinums[5] | (asciinums[4] << 4)); 
     fvalbytes[3] = (unsigned char) (asciinums[7] | (asciinums[6] << 4)); 
#else 
     // Since incoming values are little endian, and we're on big endian 
     // flip the byte order 
     fvalbytes[3] = (unsigned char) (asciinums[1] | (asciinums[0] << 4)); 
     fvalbytes[2] = (unsigned char) (asciinums[3] | (asciinums[2] << 4)); 
     fvalbytes[1] = (unsigned char) (asciinums[5] | (asciinums[4] << 4)); 
     fvalbytes[0] = (unsigned char) (asciinums[7] | (asciinums[6] << 4)); 
#endif 
     return true; 
    } 
1

Creo que todo el negocio de istringstring es excesivo. Es mucho más fácil analizarlo usted mismo un dígito por vez.

En primer lugar, crear una función para convertir un dígito hexadecimal en un entero:

signed char htod(char c) 
{ 
    c = tolower(c); 
    if(isdigit(c)) 
    return c - '0'; 

    if(c >= 'a' && c <= 'f') 
    return c - 'a'; 

    return -1; 
} 

después simplemente convertir la cadena en un entero. El siguiente código no verifica si hay errores y asume una gran permanencia, pero debe poder completar los detalles.

unsigned long t = 0; 
for(int i = 0; i < s.length(); ++i) 
    t = (t << 4) & htod(s[i]); 

A continuación, el flotador es

float f = * (float *) &t; 
+0

Creo que quería decir (c - 'A') + 10; asumiendo que solo estará en mayúscula A –

+0

Además, la ventaja de hacerlo dígito por dígito es que puede hacer un bucle de izquierda a derecha o de derecha a izquierda dependiendo de la endianidad –

+0

@OrgnlDave - por eso 'tolower' está allí. Sí en la endianidad, aunque se vuelve un poco más complicado (para un dígito de un solo byte no se intercambia) –

1

El siguiente es el código actualizado modificado para quitar el bloqueo #ifdef BIG_ENDIAN. Utiliza una técnica de lectura que debe ser independiente del orden del byte. Hace esto leyendo los bytes hexadecimales (que son poco endian en la cadena fuente) en un formato de cadena endian grande compatible con el operador iostream std :: hex. Una vez en este formato, no debería importar cuál es el orden de bytes del host.

Además, se soluciona un error en el que rawhex debe terminarse en cero para insertarse en convert sin dejar basura en algunos casos.

No tengo un gran sistema endian para probar, así que por favor verifique en su plataforma. Esto fue compilado y probado bajo Cygwin.

#include <sstream> 
#include <iostream> 

int main() 
{ 
    // The hex-encoded string, and number of values are loaded from a file. 
    // The num_vals might be wrong, so some basic error checking is needed. 
    std::string inputstring = "0000003F0000803FAD10753F00000080"; 
    int num_vals = 4; 
    std::istringstream ss(inputstring); 
    size_t const k_DataSize = sizeof(float); 
    size_t const k_HexOctetLen = 2; 

    for (uint32_t i = 0; i < num_vals; ++i) 
    { 
     char rawhex[k_DataSize * k_HexOctetLen + 1]; 

     // read little endian string into memory array 
     for (uint32_t j=k_DataSize; (j > 0) && ss.good(); --j) 
     { 
      ss.read(rawhex + ((j-1) * k_HexOctetLen), k_HexOctetLen); 
     } 

     // terminate the string (needed for safe conversion) 
     rawhex[k_DataSize * k_HexOctetLen] = 0; 

     if (ss.good()) 
     { 
      std::stringstream convert; 
      convert << std::hex << rawhex; 
      uint32_t val; 
      convert >> val; 

      std::cerr << (*(float*)(&val)) << "\n"; 
     } 
     else 
     { 
      std::ostringstream os; 
      os << "Not enough values in LUT data. Found " << i; 
      os << ". Expected " << num_vals; 
      std::cerr << os.str() << std::endl; 
      throw std::exception(); 
     } 
    } 
} 
+0

Esto se ve mucho mejor, pero algunos valores se leen de forma incorrecta en comparación con el código original. He actualizado la cadena de ejemplo con 'AD10753F', que debería ser de aproximadamente 0.9ish, pero se lee como 4.6e-41 más o menos con este – dbr

+1

' ntohl' es incorrecto para este uso: convierte big-endian en native-endian , mientras que la conversión deseada es little-endian para native-endian. – ephemient

+0

Esta versión corrige un problema de orden de cadena en la primera versión. Los tres valores de prueba se muestran correctamente en una pequeña máquina endian. Por favor, verifique en un gran sistema endian si tiene acceso. –