Ich arbeite in Embedded C mit einem Beschleunigungssensor, der Daten als 14-Bit-2er-Komplementärzahl zurückgibt. Ich speichere dieses Ergebnis direkt in uint16_t
. Später in meinem Code versuche ich diese "rohe" Form der Daten in eine vorzeichenbehaftete ganze Zahl zu konvertieren, um mit dem Rest meines Codes zu arbeiten.
Ich habe Schwierigkeiten, den Compiler zu verstehen, was ich versuche zu tun. Im folgenden Code überprüfe ich, ob das 14. Bit gesetzt ist (was bedeutet, dass die Zahl negativ ist) und dann möchte ich die Bits invertieren und 1 hinzufügen, um die Größe der Zahl zu erhalten.
%Vor% Dieser Code funktioniert leider nicht. Die Disassemblierung zeigt mir, dass der Compiler aus irgendeinem Grund meine Aussage optimiert raw_signed = -(~raw + 1);
Wie erreiche ich das gewünschte Ergebnis?
Die Mathematik funktioniert auf dem Papier, aber ich habe das Gefühl, dass der Compiler aus irgendeinem Grund mit mir kämpft: (.
Die Umwandlung des 14-Bit-Zweierkomplementwertes in 16-Bit-Vorzeichen, während der Wert beibehalten wird, ist einfach ein Ausdruck von:
%Vor% Die Linksverschiebung drückt das Vorzeichenbit in die 16-Bit-Vorzeichenbitposition, die Division durch vier stellt die Größe wieder her, behält aber ihr Vorzeichen bei. Die Teilung vermeidet das implementierungsdefinierte Verhalten einer Rechtsverschiebung, führt jedoch normalerweise zu einem einzigen arithmetischen Verschiebungsrecht an Befehlssätzen, die dies erlauben. Die Umwandlung ist notwendig, weil raw << 2
ein int
Ausdruck ist, und wenn int
nicht 16 Bit ist, wird die Division einfach den ursprünglichen Wert wiederherstellen.
Es wäre jedoch einfacher, die Beschleunigungsmesserdaten nur um zwei Bits nach links zu verschieben und sie so zu behandeln, als ob der Sensor an erster Stelle 16 Bit wäre. Normalisieren alles auf 16 Bit hat den Vorteil, dass der Code keine Änderung benötigt, wenn Sie einen Sensor mit einer beliebigen Anzahl von Bits bis zu 16 verwenden. Die Magnitude wird einfach vier mal größer, und die niedrigstwertigen zwei Bits werden Null sein - keine Information ist gewonnen oder verloren, und die Skalierung ist in jedem Fall beliebig.
%Vor%Wenn Sie in beiden Fällen die Vorzeichen ohne Vorzeichen wollen, dann ist das einfach:
%Vor%Ich würde stattdessen einfache Arithmetik machen. Das Ergebnis ist ein 14-Bit-Zeichen, das als Zahl von 0 bis 2 ^ 14 - 1 dargestellt wird. Testen Sie, ob die Zahl 2 ^ 13 oder höher ist (bedeutet ein Negativ) und subtrahieren Sie dann 2 ^ 14.
%Vor%Bitte überprüfen Sie meine Arithmetik. (Habe ich 13 und 14 richtig?)
Nehmen wir an, dass int
in Ihrer speziellen C-Implementierung 16 Bits breit ist, erzeugt der Ausdruck (1 << 15)
, den Sie in mangling raw
verwenden, undefiniertes Verhalten. In diesem Fall kann der Compiler Code erzeugen, um so ziemlich alles - oder nichts - zu tun, wenn der Zweig der Bedingung genommen wird, in dem dieser Ausdruck ausgewertet wird.
Auch wenn int
16 Bits breit ist, haben der Ausdruck -(~raw + 1)
und alle Zwischenwerte den Typ unsigned int
== uint16_t
. Dies ist ein Ergebnis der "üblichen arithmetischen Konvertierungen", da (16-Bit) int
nicht alle Werte vom Typ uint16_t
darstellen kann. Das Ergebnis wird das hohe Bit gesetzt haben und daher außerhalb des Bereichs liegen, der durch den Typ int
dargestellt werden kann, so dass es einem lvalue vom Typ int
zugewiesen wird, was implementationsdefiniertes Verhalten erzeugt. Sie müssen in Ihrer Dokumentation nachsehen, ob das von Ihnen definierte Verhalten das ist, was Sie erwartet und gewünscht haben.
Wenn Sie stattdessen eine 14-Bit-Vorzeichenkonvertierung durchführen und die höherwertigen Bits ausschalten ( (~raw + 1) & 0x3fff
), dann ist das Ergebnis - das Inverse des gewünschten negativen Werts - durch eine 16-Bit-Vorzeichenangabe% co_de darstellbar %, daher ist eine explizite Konvertierung in int
gut definiert und behält den (positiven) Wert bei. Das gewünschte Ergebnis ist das Gegenteil davon, das Sie einfach durch Negieren erhalten können. Insgesamt:
Natürlich, wenn int16_t
in Ihrer Umgebung breiter als 16 Bits sind, sehe ich keinen Grund, warum Ihr ursprünglicher Code nicht wie erwartet funktionieren würde. Das würde den obigen Ausdruck jedoch nicht ungültig machen, was unabhängig von der Größe von default int
zu einem konsistent definierten Verhalten führt.