signed 24-bit nach int



  • Hallo,

    ich bekomme über ein char Array 24-bit binäre Daten übergeben. Diese binäre Daten sind vorzeichenbehaftet.

    Um sie richtig weiterverarbeiten zu können, muss ich sie in ein int bekommen. Nun hab ich aber das Problem, dass wenn ich per memcpy die 24-bit in ein int kopiere, ich aus 24-bit signed 24-bit unsigned mache, da für das int das MSB ja nie 1 wird.

    Das einzige, was mir einfallen würde, wäre über das 2er Komplement aus den signed Werten unsigned Werte zu machen und die dann wieder auf 32-bit bezogen zu signed Werten zu machen. Wie das genau aussehen könnte, muss ich mir aber erst noch überlegen.

    Habt ihr noch andere Ideen?

    Gruß

    Shinzo



  • Ich denke du musst deine Daten einfach nur sign extenden, d.h. den Wert des höchstwertigen Bit in den 24bit Daten in alle höherwertige Bits der 32bit Daten replizieren...



  • Muss ich das MSB von den 24-bit, nachdem ich es an das MSB der 32-bit kopiert habe, zurücksetzen, oder bleibt das so wie es ist?



  • vielleicht einfach so?

    int32_t val = foo();
    if (val & (1 << 23))
       val -= (1 << 24);
    


  • Kannst du deinen Code bitte etwas erläutern? Ich versteh nicht was der machen soll. Und was ist foo()?



  • Schreib doch mal, wie bspw. -1 in deinem 24 Bit-Wert aussieht.
    Ist es

    10000000 00000000 00000001
    oder
    11111111 11111111 11111111
    ???
    als 32 Bit int:
    11111111 11111111 11111111 11111111
    


  • Ja der Vorschlag von dot kann überhaupt nicht gehen, da ich eine normale Darstellung des 2er Komplements habe, nur eben auf 24-bit bezogen und nicht auf 32-bit bezogen.

    Daher sieht bei mir eine -1 so aus:

    11111111 11111111 11111111
    


  • // i24 ist der 24 Bit-Wert
    int j;
    if((i24>>23) & 1) // negativ
    {
    	i24 &= ~(1<<23); // Vorzeichenbit löschen
    	j = i24-(1<<23); // zuweisen
    }
    else
    {
    	j = i24; // wenn die Zahl>=0, ist es kein Problem
    }
    for(int i=31;i>=0;--i) // zur Überprüfung Bits anzeigen
    {
    	cout << ((j >>i) & 1)?'1':'0';
    	if(!(i%8))
    		cout << " ";
    }
    

    So sollte das eigentlich klappen...

    Es kommt natürlich drauf an, in welcher Struktur der 24 Bitwert vorliegt, es muss also entsprechend umgerechnet werden, um auf das Vorzeichenbit zu kommen.



  • RyoShinzo schrieb:

    Ja der Vorschlag von dot kann überhaupt nicht gehen, da ich eine normale Darstellung des 2er Komplements habe

    Doch, eben genau wegen der Zweierkomplementdarstellung geht es so...

    Nehmen wir die Zahl -5, als 24 bit Zahl -5 = 11111111 11111111 11111011b
    Höchstwertiges bit ist 1, auf die restlichen 32bit repliziert ergibt das die 32bit Zahl 11111111 11111111 11111111 11111011b = -5

    Oder kurz gesagt: Wenn das 24ste Bit 1 ist dann kommt ein Byte mit 0xFF vorne dran und sonst eines mit 0x00.



  • RyoShinzo schrieb:

    Kannst du deinen Code bitte etwas erläutern? Ich versteh nicht was der machen soll. Und was ist foo()?

    Ich gehe davon aus dass die 24 Bit Zahlen als Zweierkomplement vorliegen. (Wenn das nicht so ist muss man anders rechnen.)

    int32_t val;
    
    // Hier irgendwie die 24 Bit Zahl 1:1 als Bitmuster in "val" reinstopfen,
    // natürlich unter Berücksichtigung der Endianness.
    // z.B. mit dem von dir erwähnten memcpy()
    // Die oberen 8 Bit von "val" müssen danach 0 sein.
    // Wenn das nicht sicher ist, kann man es folgenermassen erzwingen:
    //    val &= ((1 << 24) - 1);
    // (Diesen ganzen Teil sollte foo() darstellen. Ich hatte gedacht dass das klar wäre.)
    
    if (val & (1 << 23)) 
       val -= (1 << 24);
    

    Und warum sollte das funktionieren?

    Weil Zahlen im Zweierkomplement eben so funktionieren.
    Angenommen du hast eine 3 Bit Zahl im Zweierkomplement, dann stellen die 8 Bitmuster folgende Zahlen dar:

    2's compl. signed    unsigned
    000 =  0                    0
    001 =  1                    1
    010 =  2                    2
    011 =  3                    3
    100 = -4                    4
    101 = -3                    5
    110 = -2                    6
    111 = -1                    7
    

    Was wir nun machen wollen ist die Zahl in der "unsigned" Spalte (die haben wir ja) in die Zahl in der "2's compl. signed" Spalte zu konvertieren.

    Wenn du dir jetzt den Unterschied zwischen den beiden Spalten ansiehst, wirst du feststellen, dass die Differenz immer 0 oder 8 ist. Und zwar immer 0 wenn das "MSB" 0 ist, und 8 wenn das "MSB" 1 ist.
    D.h. wir müssen nurmehr abhängig vom "MSB" 8 abziehen, oder eben auch nicht.

    Genau das macht mein Code.

    Natürlich geht das ganze auch ohne "if", das sähe dann so aus:
    (Wieder bezogen auf das 3-Bit Beispiel, ist einfacher)

    int32_t val;
    
    // ... siehe oben ...
    
    int32_t msb = val & (1 << 2); // bzw. val & (1 << 23) bei 24 Bit
    int32_t offset = msb << 1;
    
    val -= offset;
    
    // oder in einer Zeile:
    // val -= (val & (1 << 2)) << 1;
    

    Als erstes holen wir uns das "msb" der originalen Zahl, also das 3. Bit (2 von Null weg gezählt), indem wir alles andere ausmaskieren (&).

    Die Variable "msb" ist danach entweder 4 (binär 100) oder 0.
    Der Offset den wir brauchen ist allerdings 8 (binär 1000) wenn das "msb" gesetzt war, bzw. 0 wenn es 0 war.
    Und den bekommen wir einfach indem wir "msb" nochmals um ein Bit nach links schieben. Dadurch bleibt 0 unverändert, und 4 wird zu 8.

    Danach ziehen wir den Offset einfach von "val" ab -> fertig.

    Das schöne daran ist, dass es unabhängig davon funktioniert, was die Plattform auf der der C(++) Code läuft für eine Darstellung für vorzeichenbehaftete Zahlen verwendet.



  • #include <stdio.h>
    
    int main( void )
    {
        char    a[3] = { -1, 5, 3 };
        long    result;
    
        result = a[0];
        result <<= 16;
        printf( "%lx\n", result );
        result |= ((unsigned char)a[1])<<8 | (unsigned char)a[2];
    
        printf( "%lx\n", result );
    
        return 0;
    }
    


  • hustbaer schrieb:

    ...

    👍

    Nur ein kleiner Haken: Falls ein int nur 16-bittig ist (beispielsweise) , dann ist 1 << 23 und 1 << 24 undefiniert. Man müsste also streng genommen

    if (val & (int32_t(1) << 23)) 
       val -= (int32_t(1) << 24);
    

    schreiben.

    Ohne Verzweigung müsste es aber auch gehen:

    int32_t val = ...; // Zahl zwischen 0...pow(2,24)-1
    val -= (val & (int32_t(1) << 23)) << 1;
    

    kk



  • @dot: Sorry, hatte deinen Post erst falsch gelesen, dachte du wolltest nur das MSB auf 1 bringen. Hab mich an deiner Idee orientiert und nun folgenden Code dazu geschrieben:

    if ((val24 & (0x00800000)) == 0x00800000) val24 |= 0xFF000000);
    

    Danke für eure Hilfe!

    Gruß

    Shinzo



  • RyoShinzo schrieb:

    if ((val24 & (0x00800000)) == 0x00800000) val24 |= 0xFF000000);
    

    ...ruft wahrscheinlich implementierungsabhängiges Verhalten hervor. Angenommen, val24 ist ein int und angenommen ints sind 32-Bit breit. Dann passiert bei val24 |= 0xFF000000; folgendes:

    • 0xFF000000 ist ein Literal vom Typ unsigned int (§2.13.1/2, integer literals)
    • val24 wird zu unsigned int konvertiert (§5/9, usual arithmetic conversions)
    • Der Operator '|' wird angewendet
    • Das Ergebnis wird in val24 geschrieben, passt aber nicht rein.

    Wenn Du eine Zahl einer vorzeichenbehafteten Ganzzahlvariablen zuordnen willst, wobei die Zahl außerhalb des gültigen Wertebereichs des Zieltyps liegt, ist das Verhalten implementierungsabhängig (§4.7/3, integral conversions). "Implementierungsabhängig" heißt, C++ schreibt nicht vor, was passieren soll, zwingt aber Compiler-Hersteller zur Dokumentation des Verhaltens.



  • krümelkacker schrieb:

    Ohne Verzweigung müsste es aber auch gehen:

    int32_t val = ...; // Zahl zwischen 0...pow(2,24)-1
    val -= (val & (int32_t(1) << 23)) << 1;
    

    kk

    Bis auf den Cast nach int32_t hab' ich das ja auch 1:1 so in meinem 2. Beitrag beschrieben.

    @RyoShinzo:
    Nimm die "ohne-if" Variante von meinem Code bzw. besser: die von krümelkacker korrigierte Version davon.
    Damit hast du AFIAK keinen plattformabhängigen Code mehr in der Konvertierung.



  • @krümelkacker: Warum passt das Ergebnis von 2 32-int Variablen, die verodert wurden, nicht wieder in eine 32-bit Variable? Das versteh ich nicht.


Anmelden zum Antworten