Unterschiedliche Endianness testen


  • Mod

    no_code schrieb:

    SeppJ schrieb:

    64-Bit Bytes

    und ich dachte das wär ein schreib fehler 😉

    btw. meine 8bit bezogen sich natürlich auf CHAR_BIT...

    Hab's gefunden:
    http://www.c-plusplus.net/forum/viewtopic-var-t-is-265718-and-postorder-is-asc-and-start-is-10.html
    Waren 16-Bit Bytes, short war 64 Bit.



  • krümelkacker schrieb:

    Warum gefällt Dir eigentlich die Lösung nicht, bei der Du gar nicht wissen musst, in welcher Reihenfolge die Bytes eines ints abgelegt werden? Das ist doch das einfachste...

    ...
    inline uint_least32_t readU32le(const unsigned char *data)
    {
      return static_cast<uint_least32_t>(data[0])
      |     (static_cast<uint_least32_t>(data[1]) << 8)
      |     (static_cast<uint_least32_t>(data[2]) << 16)
      |     (static_cast<uint_least32_t>(data[3]) << 24);
    }
    ...
    

    Das funzt garantiert überall da, wo CHAR_BIT==8 gilt und man sich ein entsprechendes typedef für uint_least32_t "besorgt" hat -- echt jetzt. Die Daten der Binärdatei würdest Du in einen char-Puffer lesen und dann diese Funktion mit entsprechendem Zeiger einfach aufrufen und gut ist...

    Es ist nicht so, dass es mir nicht gefällt. Es ist viel mehr so, dass ich (wie anfangs erwähnt) nicht so sicher im Rumschubsen von Bytes bin. 😃
    Mir war also gar nicht bewusst, dass diese Funktion bereits eine geeignete Lösung ist, sorry.

    Was macht diese Funktion denn genau? Ich kann also mit fread einfach ein char[4] lesen und übergebe ihn dann dieser Funktion und ich erhalte eine Zahl mit der ich rechnen kann. Egal welche Endianness der verwendete Computer hat?

    Ist es wirklich so einfach? Wenn ja, warum funktioniert diese Funktion überhaupt? Was ist der Trick? 🙂



  • Der Operator << schiebt die Bits in Richtung der größeren Zahl. (soweit ich das verstehe)

    Also:

    die 32 Bits:                         deine Ankommenden Bits:
    00000000 00000000 00000000 00000000  10101001 <<  8
    00000000 00000000 00000000 10101001  00011001 << 16
    00000000 00000000 00011001 10101001  usw...
    

  • Mod

    MrEndianness schrieb:

    Es ist nicht so, dass es mir nicht gefällt. Es ist viel mehr so, dass ich (wie anfangs erwähnt) nicht so sicher im Rumschubsen von Bytes bin. 😃
    Mir war also gar nicht bewusst, dass diese Funktion bereits eine geeignete Lösung ist, sorry.

    Was macht diese Funktion denn genau? Ich kann also mit fread einfach ein char[4] lesen und übergebe ihn dann dieser Funktion und ich erhalte eine Zahl mit der ich rechnen kann. Egal welche Endianness der verwendete Computer hat?

    Ist es wirklich so einfach? Wenn ja, warum funktioniert diese Funktion überhaupt? Was ist der Trick? 🙂

    Der Linksschiebeoperator << x ist eigentlich nur eine elegante Möglichkeit um zu sagen "multipliziere die Zahl mit 2 hoch x". Also übersetzt:

    return static_cast<uint_least32_t>(data[0] * pow(2,0)))
      |     (static_cast<uint_least32_t>(data[1]) * pow(2,8))
      |     (static_cast<uint_least32_t>(data[2]) * pow(2,16))
      |     (static_cast<uint_least32_t>(data[3]) * pow(2,24));
    

    Nun erinnern wir uns an die Potenzgesetze und nutzen, dass 2^(a*b) = 2ab ist. Somit kann man schreiben:

    return static_cast<uint_least32_t>(data[0] * pow(pow(2,8),0))) //  0 = 8*0
      |     (static_cast<uint_least32_t>(data[1]) * pow(pow(2,8),1))  // 8 = 8 * 1
      |     (static_cast<uint_least32_t>(data[2]) * pow(pow(2,8),2))  //16 = 8 * 2
      |     (static_cast<uint_least32_t>(data[3]) * pow(pow(2,8),3)); //24 = 8 * 3
    

    Jetzt setzen wir 2^8=256 ein:

    return static_cast<uint_least32_t>(data[0] * pow(256,0))) 
      |     (static_cast<uint_least32_t>(data[1]) * pow(256,1)) 
      |     (static_cast<uint_least32_t>(data[2]) * pow(256,2)) 
      |     (static_cast<uint_least32_t>(data[3]) * pow(256,3));
    

    Wenn wir uns jetzt erinnern, dass CHAR_BIT=8 ist, dann fällt uns auf, größte Wert den ein char mit 8 Bit haben kann 256 ist, denn 2^8=256. Und jetzt gucken wir mal im Binärsystem welche Werte data[0] * pow(256,0) haben kann:

    pow(256,0) ist einfach 1 (binär wie dezimal)
    255 ist in binär 11111111
    Wenn wir 1 mit werten zwischen 0 und 11111111 malnehmen bekommen wir 
    logischerweise nur Werte zwischen 0 und 11111111 heraus.
    
    Fein, gucken wir nun data[1] * pow(256,1) an:
    pow(256,1) ist 256, in binär 100000000 (eine 1 mit 8 Nullen)
    Was kommt raus, wenn wir eine Zahl zwischen 0 und 11111111 (255, acht Einsen)
     mit 100000000 multiplizieren?
    Die Regel, dass man eine Zahl mit 10 multipliziert indem man eine Null 
    dranhängt gilt auch im binären (sogar in allen Zahlensystemen, rechne mal 
    nach), das heißt z.B. 110101 * 10 = 1101010 (dezimal steht da: 53 * 2 = 106. 
    Passt.). Das gilt natürlich auch für Multiplikation mit 100: 1101 * 100 = 
    110100 (13*4=52 Passt.) Und so weiter.
    Das heißt,  wenn wir mit 100000000 multiplizieren hat das Ergebnis am Ende 8 
    Nullen. Was ist die größte Zahl die wir erhalten können?
    11111111 * 100000000 = 11111111 00000000
    Und der kleinste Wert (abgesehen von Null)?
    00000001 * 100000000 = 00000001 00000000
    
    Analog sehen wir, dass die dritte Zeile der Rechnung immer nur Werte mit 16 
    Nullen am Ende und die vierte mit 24 Nullen am Ende ergibt.
    
    Jetzt gucken wir aber mal, was passiert, wenn man den ODER Operator | benutzt 
    um zum Beispiel die erste mit der zweiten Zeile zu verknüpfen:
    Die erste Zeile hat nur Werte der Form
    00000000 XXXXXXXX  (X ist 1 oder 0)
    Die zweite Zeile hat nur Werte der Form
    YYYYYYYY 00000000
    Oho, da kommt also bei ODER-Verknüpfung dies heraus:
    YYYYYYYY XXXXXXXX
    
    (Jetzt sieht man auch schon so langsam worauf das hinausläuft: Wenn der char 
    data[0] vorher den Wert XXXXXXXX und der char[1] vorher den Wert YYYYYYYY 
    hatte, dann ist das Ergebnis YYYYYYYY XXXXXXXX genau das Bitmuster das wir 
    haben wollten.)
    

    Formell haben wir da eine Addition durchgeführt (nachrechnen!), d.h. man kann den ODER Operator auch durch ein + ersetzen:

    return static_cast<uint_least32_t>(data[0] * pow(256,0))) 
      +     (static_cast<uint_least32_t>(data[1]) * pow(256,1)) 
      +     (static_cast<uint_least32_t>(data[2]) * pow(256,2)) 
      +     (static_cast<uint_least32_t>(data[3]) * pow(256,3));
    

    In dieser Schreibweise sehen wir auch, dass hier ein Stellenwertsystem mit der Basis 256 vorliegt. Die Werte in den chars sind die Ziffern und wir berechnen nach den üblichen Formeln den Wert einer Zahl aus ihrer Zifferndarstellung. Genauso wie wir den Wert der Hexadezimalzahl A2D3 berechnen würden (A ist 10 im Dezimalsystem und D ist 13):
    A2D3 = 10 * 16 ^ 3 + 2 * 16 ^ 2 + 13 * 16 ^ 1 + 3 * 16 ^ 0 = 10*4096 + 3*256 + 13*16 + 3*1 = 41 683
    Nur hier eben mit 256 als Basis und nicht 16. Und Endianess haben wir nirgendwo benutzt, bloß gutes altes Potenzieren, Multiplikation und Addition. Das heißt, das Ergebnis ist völlig unabhängig von der internen Darstellung der Zahlen.

    Man probiere übrigens alle hier gezeigten Programme einmal aus und überzeuge sich, dass sie tatsächlich für jeden Umformungsschritt das gleich Ergebnis liefern.

    P.S.: Autsch, das ist lang geworden, das werde ich nicht alles Korrektur lesen. Wenn jemand Fehler sieht oder Verbesserungsvorschläge hat, bitte melden.



  • Wow, SeppJ. Danke für deine große Bemühung mit das so genau zu erklären.
    Das hilft mir unglaublich weiter. Viele vielen Dank. 👍


Anmelden zum Antworten