Vorzeichenerweitern einer Neun-Bit-Zahl in C

Lesezeit: 7 Minuten

Benutzer-Avatar
Chris Lang

Ich habe einen kurzen, instrdas sieht so aus:

1110xxx111111111

Ich muss die Bits 0-9 herausziehen, was ich mache (instr & 0x1FF). Diese Menge wird dann in einem neuen Kurzschluss gespeichert. Das Problem ist, dass wenn dies auftritt, es wird 0x0000000111111111nicht 0x1111111111111111 wie ich will. Wie kann ich das beheben? Vielen Dank!

BEARBEITEN

Hier ist der Code:

short instr = state->mem[state->pc];
unsigned int reg = instr >> 9 & 7; // 0b111
state->regs[reg] = state->pc + (instr & 0x1FF);

Dies ist ein Simulator, der in Assembly liest. state ist die Maschine, regs[] sind die Register und pc ist die Adresse der aktuellen Anweisung in mem[].

Dies ist in Ordnung, wenn die letzten neun Bits eine positive Zahl darstellen, aber wenn sie -1 darstellen, werden sie als reine Einsen gespeichert, was von meinem Code als positiver Wert interpretiert wird.

  • Ich bin mir nicht sicher, ob ich die Frage oder den von Ihnen verwendeten Prozess verstehe. Möchten Sie den Code teilen, den Sie verwenden, um diese Ergebnisse zu erhalten?

    – Spaß

    28. April 2011 um 5:37 Uhr

  • Dabei ist nicht klar, wer das werden soll 0x1111111111111111?

    – MByD

    28. April 2011 um 5:43 Uhr

Benutzer-Avatar
Ben Jackson

Angenommen, ein Kurzschluss ist 16 Bit:

Sie können es manuell tun: (instr & 0x1FF) | ((instr & 0x100) ? 0xFE00 : 0). Dies testet das Vorzeichenbit (das oberste Bit, das Sie behalten, 0x100) und setzt alle Bits darüber, wenn das Vorzeichenbit gesetzt ist. Sie können dies auf 5 Bit erweitern, indem Sie die Masken an anpassen 0x1F, 0x10 und 0xFFE0wobei es sich um die unteren 5 Bits handelt, das 5. Bit selbst und alle Bits 5–16.

Oder Sie finden eine Entschuldigung, um die Bits dem oberen Teil eines vorzeichenbehafteten Kurzschlusses zuzuweisen und sie nach unten zu verschieben (wobei Sie dabei eine Vorzeichenerweiterung erhalten): short x = (instr & 0x1FF) << 7; x >>= 7; Letzteres kann tatsächlich einfacher in der Montage sein und erfordert keinen Zweig. Wenn instr ist unterzeichnet Dies kann in einem einzigen Ausdruck erfolgen: (instr & 0x1FF) << 7 >> 7. Da dies bereits die oberen Bits entfernt, vereinfacht es sich instr << 7 >> 7. Ersetzen Sie 7 durch 11 für 5 Bits (16-5).

  • Wie könnte dies für eine 5-Bit-Zahl angepasst werden? Ich muss beide Arten in diesem Programm handhaben.

    – Chris Lange

    28. April 2011 um 5:50 Uhr

  • Bei letzterem könntest du das nicht einfach tun (instr & 0x1FF << 7) >> 7?

    – Joel Lee

    28. April 2011 um 5:52 Uhr


  • +1, aber beim Verschieben nach links und dann nach rechts sollte funktionieren (solange Sie einen signierten Typ verwenden), aber ich wäre nicht überrascht, wenn einige relativ obskure Compiler (möglicherweise auf eingebetteten Plattformen) dies fälschlicherweise optimieren würden, vorausgesetzt, dass Die beiden Verschiebungen heben sich auf und bemerken den Vorzeichenerweiterungsnebeneffekt nicht. Ich erwähne dies, weil der Codestil am wahrscheinlichsten bei einer eingebetteten Plattform auftritt, wenn ein Wert aus einem Hardwareregister extrahiert wird.

    Benutzer180247

    28. April 2011 um 6:01 Uhr

  • @Ben Jackson, @Steve314 macht sich zu Recht Sorgen über diese signierte Rechtsverschiebung. Das Ergebnis der vorzeichenbehafteten Rechtsverschiebung ist nicht durch den Standard definiert. Natürlich funktioniert Ihre erste Lösung – solange Sie die Prämisse von OP akzeptieren short ist 16 Bit (ebenfalls nicht garantiert).

    – rlibby

    28. April 2011 um 6:23 Uhr

  • @BenJackson Folgendes ist etwas kürzer zu lesen und unabhängig von der Wortgröße: (instr & 0x1FF) | -(instr & 0x100).

    – rtx13

    27. Januar 2021 um 4:30 Uhr

Benutzer-Avatar
nimrodm

* Keine Verzweigung erforderlich *

Sehen http://graphics.stanford.edu/~seander/bithacks.html#FixedSignExtend für eine Liste sehr nützlicher Bit-Hacks. Insbesondere ist das Zeichen, das eine Zahl erweitert, so einfach wie:

/* generate the sign bit mask. 'b' is the extracted number of bits */
int m = 1U << (b - 1);  

/* Transform a 'b' bits unsigned number 'x' into a signed number 'r' */
int r = (x ^ m) - m; 

Möglicherweise müssen Sie die obersten Bits von ‘x’ löschen, wenn sie nicht Null sind ( x = x & ((1U << b) - 1); ), bevor Sie das obige Verfahren anwenden.

Wenn die Anzahl der Bits ‘b’ zur Kompilierzeit bekannt ist (z. B. 5 Bits in Ihrem Fall), gibt es sogar eine einfachere Lösung (dies könnte eine bestimmte Vorzeichenerweiterungs-Anweisung auslösen, wenn der Prozessor dies unterstützt und der Compiler schlau genug ist):

struct {signed int x:5;} s;
r = s.x = x;

(instr & 0x1FF) * (1 - ((unsigned short)(instr & 0x100) >> 7))

Wie funktioniert es? Es wählt Ihr Vorzeichenbit aus und verschiebt es auf die Position der 2. Dies wird verwendet, um entweder den Wert 1 (wenn Ihr Vorzeichenbit fehlte) oder -1 (wenn Ihr Vorzeichenbit vorhanden war) zu erzeugen.

Diese Lösung ist verzweigungslos und hängt nicht von undefiniertem Verhalten ab.

  • Auf einem unbekannten Mikrocontroller würde ich wahrscheinlich lieber verzweigen als multiplizieren. Die Verzweigungsstrafe auf solchen CPUs ist gering und die Strafe, wenn es keinen Hardware-Multiplikator gibt (oder vielleicht sogar, wenn es einen gibt!), ist viel höher. In der Tat, angesichts des Risikos von nein Barrel-Shifter vielleicht ist der einfache Zweig/Test am besten.

    – Ben Jackson

    28. April 2011 um 6:38 Uhr

Dies ist eher eine Verfeinerung früherer Antworten, aber bisher wurde keine vollständig generische Lösung vorgestellt. Dieses Makro signiert einen Wert v mit sb Angabe der 0-basierten Bitnummer des Vorzeichenbits.

#define SIGNEX(v, sb) ((v) | (((v) & (1 << (sb))) ? ~((1 << (sb))-1) : 0))

int32_t x;

SIGNEX(x, 15); // Sign bit is bit-15 (16th from the right)
SIGNEX(x, 23); // Sign bit is bit-23 (24th from the right)

Es verwendet Verzweigung, um die Portabilität über Plattformen hinweg zu maximieren, denen ein Hardware-Multiplizierer oder Barrel-Shifter fehlt.

Ich bin mir nicht sicher, wie Sie nach dem Maskieren mit 13 1 Bits kommen 0x1ff, aber dies sollte eine 9-Bit-Zahl in eine 16-Bit-Kurzzeichenerweiterung vorzeichenerweitern. Nicht schön (oder besonders effizient), aber es funktioniert:

(instr & 0x1ff) | (0xfe00 * ((instr & 0x100) >> 8))

Maskieren Sie das Vorzeichenbit, verschieben Sie es auf die 1-Position, um 0/1 zu erhalten. Multiplizieren Sie dies mit den oberen Bits, wenn das Vorzeichen 1 ist, dann wird die 9-Bit-Zahl mit ODER verknüpft 0xfewodurch alle oberen Bits auf 1 gesetzt werden.

  • Habe dies noch nicht getestet, aber wie könnte dies für eine 5-Bit-Zahl angepasst werden? Ich brauche beide Typen in diesem Programm. Und Sie haben Recht mit den Ergebnissen; Ich habe stundenlang gearbeitet, also bin ich erschöpft. 😀

    – Chris Lange

    28. April 2011 um 5:47 Uhr

  • Maskieren Sie das Vorzeichenbit und verschieben Sie dieses Bit an die Position 1 (so erhalten Sie 1, wenn Sie eine negative Zahl haben, und sonst 0). Erstellen Sie dann eine ODER-Maske für die erweiterten Bits und multiplizieren Sie diese mit dem verschobenen Vorzeichen. Wenn Ihre ursprüngliche Zahl negativ war, bleibt diese Maske nur Einsen und ansonsten nur Nullen. ODER dies mit Ihrer maskierten Zahl, um die oberen Bits mit Einsen zu füllen.

    – Hoa Long Tam

    28. April 2011 um 17:23 Uhr

Benutzer-Avatar
SurrealWombat

Ich bin gerade auf der Suche nach etwas anderem darauf gestoßen, vielleicht etwas spät, aber vielleicht ist es für jemand anderen nützlich. AFAIAC Alle C-Programmierer sollten mit der Programmierung von Assembler beginnen.

Jedenfalls ist das Erweitern von Zeichen viel einfacher als die anderen 2 Vorschläge. Stellen Sie einfach sicher, dass Sie vorzeichenbehaftete Variablen verwenden, und verwenden Sie dann 2 Verschiebungen.

short instr = state->mem[state->pc];
unsigned int reg = (instr >> 9) & 7; // 0b111
instr &= 0x1ff;  // get lower 9 bits
instr = ((instr << 7) >> 7); // sign extend
state->regs[reg] = state->pc + instr;

Wenn die Variable vorzeichenbehaftet ist, übersetzt der C-Compiler >> in arithmetische Verschiebung nach rechts, wobei das Vorzeichen erhalten bleibt. Dieses Verhalten ist plattformunabhängig.

Angenommen, instr beginnt mit 0x1ff, dann haben wir, << 7 wird SL (Shift Left) der Wert, also ist instr jetzt 0xff80, dann >> 7 wird ASR der Wert, also ist instr jetzt 0xffff.

  • Habe dies noch nicht getestet, aber wie könnte dies für eine 5-Bit-Zahl angepasst werden? Ich brauche beide Typen in diesem Programm. Und Sie haben Recht mit den Ergebnissen; Ich habe stundenlang gearbeitet, also bin ich erschöpft. 😀

    – Chris Lange

    28. April 2011 um 5:47 Uhr

  • Maskieren Sie das Vorzeichenbit und verschieben Sie dieses Bit an die Position 1 (so erhalten Sie 1, wenn Sie eine negative Zahl haben, und sonst 0). Erstellen Sie dann eine ODER-Maske für die erweiterten Bits und multiplizieren Sie diese mit dem verschobenen Vorzeichen. Wenn Ihre ursprüngliche Zahl negativ war, bleibt diese Maske nur Einsen und ansonsten nur Nullen. ODER dies mit Ihrer maskierten Zahl, um die oberen Bits mit Einsen zu füllen.

    – Hoa Long Tam

    28. April 2011 um 17:23 Uhr

Benutzer-Avatar
RPichioli

Eine einfachere Lösung ist dies z x sein 5-Bit 2‘s Komplementnummer, siehe:

z = (x^16)-16

1365690cookie-checkVorzeichenerweitern einer Neun-Bit-Zahl in C

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy