2012-04-07 5 views
6

Ich versuche, den folgenden Python-Code in C zu übersetzen ++:Auspacken hex-encoded Schwimmer

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 

So heißt es 32-Bit-Wert des Hex-codierter String, wandelt es in einen Byte-Array mit die unhexlify Methode und interpretiert es als ein Little-Endian-Float-Wert.

Die fast folgende Werke, aber der Code ist eine Art crappy (und die letzte 00000080 falsch analysiert):

#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(); 
     } 
    } 
} 

(kompiliert auf OS X 10.7/gcc-4.2.1, mit einem einfachen g++ blah.cpp)

Insbesondere würde ich gerne die BIG_ENDIAN Makro Zeug loswerden, wie ich bin sicher, es gibt eine schönere Möglichkeit, dies zu tun, wie this post diskutiert.

Wenige andere zufällige Details - Ich kann Boost nicht verwenden (zu große Abhängigkeit für das Projekt). Die Zeichenfolge enthält gewöhnlich zwischen 1536 (8 * 3) und 98304 Gleitkommawerten (32 * 3), höchstens 786432 (64 * 3)

(edit2: added anderen Wert, 00000080 == -0.0)

Antwort

0

Dies ist, was wir mit endete, OpenColorIO/src/core/FileFormatIridasLook.cpp

(Amardeep Antwort mit dem unsigned uint32_t fix wahrscheinlich auch funktionieren würde)

// 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

Ich denke, das ganze istringstring Geschäft ist ein Overkill. Es ist viel einfacher, dies selbst eine Ziffer nach der anderen zu analysieren.

Erstellen Sie zunächst eine Funktion, um eine Hexadezimalzeichens in eine ganze Zahl zu konvertieren:

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

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

    return -1; 
} 

Dann einfach die Zeichenfolge in eine ganze Zahl konvertieren. Der folgende Code prüft nicht auf Fehler und geht von Big-Endianness aus - Sie sollten jedoch in der Lage sein, die Details einzugeben.

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

Dann wird Ihr Schwimmer ist

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

Ich glaube, Sie (c - 'A') gemeint + 10; angenommen, dass es nur in Großbuchstaben A sein wird –

+0

Auch der Vorteil, es zu tun, Ziffer für Ziffer selbst ist, dass Sie von links nach rechts oder von rechts nach links in Abhängigkeit von Endian-Ness –

+0

@OrgnlDave - das ist, warum 'tolower' ist da. Ja auf der Endian-Ness, obwohl es etwas kniffliger wird (für ein einzelnes Byte Ziffern nicht vertauschen) –

1

Das Folgende ist die aktualisierte Code modifiziert, um den #ifdef BIG_ENDIAN Block zu entfernen. Es verwendet eine Lesetechnik, die unabhängig von der Host-Byte-Reihenfolge sein sollte. Dazu liest man die Hex-Bytes (die ein kleines Endian in der Quell-Zeichenkette sind) in ein großes Endian-Zeichenketten-Format, das mit dem iostream std :: hex-Operator kompatibel ist. Sobald es in diesem Format ist, sollte es egal sein, was die Host-Byte-Reihenfolge ist.

Darüber hinaus behebt es einen Fehler, rawhex muss Null beendet werden, um in convert eingefügt werden, ohne in einigen Fällen nachher Abfall.

Ich habe kein Big-Endian-System zum Testen, also bitte überprüfen Sie auf Ihrer Plattform. Dies wurde unter Cygwin zusammengestellt und getestet.

#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

Das sieht viel schöner aus, aber einige Werte werden falsch im Vergleich zum ursprünglichen Code gelesen. Ich habe den Beispielstring mit 'AD10753F' aktualisiert, der ungefähr 0,9ish sein sollte, aber gelesen wird als 4,6e-41 oder so mit diesem – dbr

+1

'ntohl' ist falsch für diese Verwendung: es konvertiert big-endian zu native-endian , während die gewünschte Umwandlung von Little-Endian zu Native-Endian ist. – ephemient

+0

Diese Version behebt ein String-Reihenfolge-Problem in der ersten Version. Alle drei Testwerte werden auf einer kleinen Endian-Maschine korrekt angezeigt. Bitte überprüfen Sie auf einem Big-Endian-System, wenn Sie Zugriff haben. –