Forum: Mikrocontroller und Digitale Elektronik C Rechnen mit signed und unsigned


von Anonymous U. (gastt)


Lesenswert?

Hallo!

Ich bin gerade etwas am verzweifeln mit C: Was passiert in folgendem 
Code?
1
int16_t diff;
2
int8_t offset = -20;
3
uint16_t adc = 300;
4
diff = (512+offset)-adc;

Mein Gedanke wäre, dass in der Klammer (512+offset) der offset in 
unsigned umgewandelt werden (bzw. vom Compiler als unsigned aufgefasst), 
da 512 auch unsigned ist. Das Ergebnis wäre dann eine große positive 
unsigned Zahl. Aber wie bekomme ich das mathematisch richtige Ergebnis, 
wenn ich so einen typen-Salat habe?

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Anonymous U. schrieb:
> da 512 auch unsigned ist

Ist es nicht. Solange Du nur einen numerischen Wert hinschreibst, ist 
der vorzeichenbehaftet.

Das kannst Du mit dem Suffix U ändern; schreib also nicht 512, sondern 
512U.

von Peter II (Gast)


Lesenswert?

Anonymous U. schrieb:
> (bzw. vom Compiler als unsigned aufgefasst),
> da 512 auch unsigned ist

das ist aber nicht so, wenn du willst das es unsigned ist dann schreibe 
es hin.


(512u+offset)-adc;

von Anonymous U. (gastt)


Lesenswert?

Rufus Τ. F. schrieb:
> Das kannst Du mit dem Suffix U ändern; schreib also nicht 512, sondern
> 512U.

Ok, dann wäre die Klammer schonmal signed. Das heißt, ich muss dann auch 
noch adc mit signed casten?

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Anonymous U. schrieb:
> Ok, dann wäre die Klammer schonmal signed.

Das sollte reichen, damit auch der ganze Ausdruck signed wird.

von Anonymous U. (gastt)


Lesenswert?

Frank M. schrieb:
> Anonymous U. schrieb:
>> Ok, dann wäre die Klammer schonmal signed.
>
> Das sollte reichen, damit auch der ganze Ausdruck signed wird.

Oh, entschuldige. Es könnten quasi auch negative Werte herauskommen:

Anonymous U. schrieb:
> int16_t diff;
> int8_t offset = -20;
> uint16_t adc = 600;
> diff = (512+offset)-adc;

diff = (512+(-20))-600 = -108

Das heißt, 512 passt als signed und ich muss adc als signed casten? 
Wahnsinn wie solche Trivialitäten verwirren können :-/

von Sebastian V. (sebi_s)


Lesenswert?

Du musst erstmal schauen wie groß welche Typen sind und dann schauen was 
passiert. Es wurde ja schon gesagt, dass 512 signed ist und der Typ ist 
int. Deine Variablen sind aber vermutlich kleiner als int (nehme ich mal 
an, int könnte auch int16_t sein). Unter diesen Voraussetzungen wird bei 
512+offset die offset Variable zu einem int konvertiert. Das ist OK, 
weil alle Zahlen eines int8_t lassen sich garantiert als int darstellen. 
Dann soll dein adc Wert abgezogen werden und dieser ist uint16_t. Nach 
meinen Voraussetzungen ist das kleiner als int also wird dies zu einem 
int konvertiert da man auch alle Werte von uint16_t als int darstellen 
kann. Am Ende kriegst du also einen int Wert von dem eventuell etwas 
abgeschnitten wird wenn er nicht in ein int16_t passt.

Jetzt die andere Alternative wenn int16_t genauso groß ist wie ein int: 
Dann haben wir bei der Subtraktion von adc die Typen int16_t und 
uint16_t. In diesem Fall ist dein Ergebnis unsigned! Wenn also bei der 
Berechnung etwas negatives rauskommt, dann kriegst du stattdessen eine 
große postive Zahl. Hier was zum testen:
1
int x = INT_MAX;
2
unsigned int y = INT_MAX+1U;
3
// x-y hat Typ unsigned und kann damit nicht -1 sein

: Bearbeitet durch User
von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Bei Addition, Subtraktion und Multiplikation spielt es keine Rolle, ob 
die Operanden signed oder unsigned sind.

Signed / Unsigned spielt nur eine Rolle

-- Für Extensions, also ob ein 8-Bit Wert, wenn er in eine 16-Bit 
Berechnung eingeht, signed oder unsigned erweitert wird.

-- Bei Überlauf: Signed / Unsigned wird beim Overflow unterschiedlich 
behandelt: Unsigned Überlauf wird modulo 2^Bitbreite gerechnet, Signed 
Overflow ist Undefined Behavior.

Falls auch Signed Overflow definiert sein soll, geht das bei GCC mit 
-fwrapv.

-- Bei saturierte Arithmetik (stdfix.h etc) spielt die Signedness eine 
Rolle, z.B. wird für saturierte Signed- resp. Unsigned-Addition 
unterschiedlicher Code erzeugt.

von Noch einer (Gast)


Lesenswert?

Wenn wir gerade dabei sind...

Es wird behauptet, der C Standard schreibe das Zweierkomplement nicht 
vor. Der Compiler könne auch eine Kodierung nutzen, bei der Signed- und 
Unsigned-Additionen unterschiedliche Ergebnisse liefern.

Stimmt das?

von Karl H. (kbuchegg)


Lesenswert?

Noch einer schrieb:
> Wenn wir gerade dabei sind...
>
> Es wird behauptet, der C Standard schreibe das Zweierkomplement nicht
> vor. Der Compiler könne auch eine Kodierung nutzen, bei der Signed- und
> Unsigned-Additionen unterschiedliche Ergebnisse liefern.
>
> Stimmt das?

Ja, das stimmt. Prinzipiell.
Allerdings waren derartige Maschinen schon damals die Ausnahme. Auf 
aktuellen Maschinen wird praktisch nur 2-er Komplement verwendet. 
Einfach weil es die beste Lösung ist.

: Bearbeitet durch User
von (prx) A. K. (prx)


Lesenswert?

Anonymous U. schrieb:
> wenn ich so einen typen-Salat habe?

Zum Typen-Salat:
512: int
offset: wird int
(512 + offset): (int + int) => int
16-Bit:  adc bleibt unsigned
         (...)-adc: int-unsigned => unsigned
>16-Bit: adc wird zu int
         (...)-adc: int-int => int
Der Typ vom Ergebnis hängt also von der Maschine ab.

von Anonymous U. (gastt)


Lesenswert?

Schonmal vielen Dank für die Erklärungen.

Sebastian V. schrieb:
> Jetzt die andere Alternative wenn int16_t genauso groß ist wie ein int:
> Dann haben wir bei der Subtraktion von adc die Typen int16_t und
> uint16_t. In diesem Fall ist dein Ergebnis unsigned!

Ich programmier grad mit AVR-Studio einen Atmega (8bit). Also würde ich 
meinen, dass der int Datentyp 8 Bit groß ist. Naja wie auch immer, ich 
will auf jedenfall etwas definiertes. Also:
1
int16_t diff;
2
int8_t offset = -20;
3
uint16_t adc = 600;
4
diff = (512+offset)-(int16_t)adc;

So sollte ich ein signed als Ergebnis erhalten, egal ob int jez 16 oder 
8 Bit breit ist.

Falls in AVR-Studio ein int wirklich 32 Bit breit wäre, so wäre das doch 
Speicherverschwendung? Würde Folgendes dann mehr Sinn machen?
1
int16_t diff;
2
int8_t offset = -20;
3
uint16_t adc = 600;
4
diff = ((int16_t)512+offset)-(int16_t)adc;

PS: Hintergrund ist, ich wollte den ADC auslesen. Dessen Wert will ich 
in die Differenz von der Mitte (512) umrechnen. Dabei soll ein Offset um 
die Mitte korrigiert werden (+-127). Demnach könnte ich ja als Offset 
auch unsigned verwenden, und dann würde es halt heißen 385+offset-adc. 
Mit signed ist der Code halt irgendwie verständlicher und schöner.

: Bearbeitet durch User
von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Anonymous U. schrieb:
> Ich programmier grad mit AVR-Studio einen Atmega (8bit). Also würde ich
> meinen, dass der int Datentyp 8 Bit groß ist.

Für Typlayout, siehe

http://gcc.gnu.org/wiki/avr-gcc

: Bearbeitet durch User
von Noch einer (Gast)


Lesenswert?

Ganz böse Falle - Im Gegensatz zum Standard rechnen die MC-Compiler mit 
8Bit statt mit int. Solange du nicht explizit vor der Addition in int 
castest.

Du musst aber an zwei Punkten höllisch aufpassen.

Wenn du "int = int8_t + int8_t" schreibst machen die meisten MC-Compiler 
eine 8Bit Addition und erweitern erst danach auf 16 Bit.

In deinem Beispiel funktioniert zufälligerweise alles. 512 passt nicht 
in 8 Bit und der Compiler erweitert beide Operanden auf 16 Bit.

Wie bei allen C Compilern sind die 8Bit->16Bit Umwandlungen für signed 
und unsigned unterschiedlich.

Für die Addition ist egal, ob Operanden und Ergebnis signed oder 
unsigned sind. Du addierst zwei Bitmuster und bekommst ein Bitmuster. 
Bei einer Addition im Zweierkomplement ist egal, ob du diese Bitmuster 
als signed oder unsigned betrachtest. int=int+int macht genau dasselbe 
wie int=uint+uint. Kannst auch alles mit uint rechnen und erst beim 
printf als %d ausgeben. Du bekommst das richtige Vorzeichen.

Solltest dir aber trotzdem angewöhnen, alles sauber als signed/unsigned 
anzulegen. Mit 512 hat das Programm funktioniert, mit 127 spinnt es. 
Solche Fehler sucht man ewig.

Und besser von vorn herein so anlegen, dass man nicht casten muss. Dann 
brauchst du dir auch keine Gedanken machen, ob der Cast 
uint8->int8->int16 oder uint8->uint16->int16 umwandelt.

von (prx) A. K. (prx)


Lesenswert?

Noch einer schrieb:
> Wenn du "int = int8_t + int8_t" schreibst machen die meisten MC-Compiler
> eine 8Bit Addition und erweitern erst danach auf 16 Bit.

Zumindest sollte man das im Auge behalten. Der hier recht beliebte 
avr-gcc macht das allerdings nicht.

von Carl D. (jcw2)


Lesenswert?

A. K. schrieb:
> Noch einer schrieb:
>> Wenn du "int = int8_t + int8_t" schreibst machen die meisten MC-Compiler
>> eine 8Bit Addition und erweitern erst danach auf 16 Bit.
>
> Zumindest sollte man das im Auge behalten. Der hier recht beliebte
> avr-gcc macht das allerdings nicht.

Und bei den Anderen handelt es sich dann nur um Compiler mit C-like 
Syntax, denn die Integerpromotion ist etwas, was ein C-Compiler machen 
muß. Zumindest solange man es per Cast nicht ausdrücklich anders 
verlangt.

BTW, daß er sich an diese Regel hält, wird avr-gcc immer wieder 
"vorgeworfen".

: Bearbeitet durch User
von Rolf M. (rmagnus)


Lesenswert?

Noch einer schrieb:
> Du musst aber an zwei Punkten höllisch aufpassen.
>
> Wenn du "int = int8_t + int8_t" schreibst machen die meisten MC-Compiler
> eine 8Bit Addition und erweitern erst danach auf 16 Bit.

Das ist dann aber kein konformes C, also eigentlich ein Fehler. Der 
Compiler darf sowas nur dann, wenn es am Ergebnis nichts ändert.

> Solltest dir aber trotzdem angewöhnen, alles sauber als signed/unsigned
> anzulegen.

gcc warnt auch, wenn man signed und unsigned mischt. Wenn er das nicht 
tut, sind die Warn-Einstellungen zu niedrig. Man sollte solches Mischen 
auch grundsätzlich meiden und da, wo es sich nicht vermeiden läßt, genau 
überlegen, was die Konsequenzen sind und dann mit Casts klar machen, wie 
es gedacht ist.

Carl D. schrieb:
> Zumindest solange man es per Cast nicht ausdrücklich anders verlangt.

Ein Cast ändert auch nichts. Auch (int8_t)100 + (int8_t)100 muß 200 
ergeben, denn die Addition wird niemals mit was kleinerem gemacht als 
int. Die Integer-Promotion wird durch den Cast nicht ausgehebelt, es 
werden nach diesem beide Operanden erstmal wieder auf int erweitert, 
bevor addiert wird.

von (prx) A. K. (prx)


Lesenswert?

Carl D. schrieb:
> Und bei den Anderen handelt es sich dann nur um Compiler mit C-like
> Syntax,

Hast du es auch eine Nummer kleiner? So streng verstanden ist avr-gcc 
das auch, denn dessen "double" Datentyp entspricht nicht den Vorgaben 
des Standards.

von (prx) A. K. (prx)


Lesenswert?

Rolf M. schrieb:
> Ein Cast ändert auch nichts. Auch (int8_t)100 + (int8_t)100 muß 200
> ergeben,

(int8_t)(100 + 100)

: Bearbeitet durch User
von Rolf M. (rmagnus)


Lesenswert?

A. K. schrieb:
> Rolf M. schrieb:
>> Ein Cast ändert auch nichts. Auch (int8_t)100 + (int8_t)100 muß 200
>> ergeben,
>
> (int8_t)(100 + 100)

Was willst du damit sagen? Auch so wird das 100 + 100 (zumindest formal) 
in int gerechnet. Die Reduktion auf 8 Bit kommt erst danach und ginge 
natürlich genauso auch ohne Cast:
1
int8_t i = 100 + 100;

von (prx) A. K. (prx)


Lesenswert?

Rolf M. schrieb:
>> (int8_t)(100 + 100)
>
> Was willst du damit sagen? Auch so wird das 100 + 100 (zumindest formal)
> in int gerechnet.

Ok, also
  if ((uint8_t)(a + b) > 100)
Das darf der Compiler ohne Cast nicht in 8 Bits rechnen. Mit schon.

von B. S. (bestucki)


Lesenswert?

Rolf M. schrieb:
> Die Reduktion auf 8 Bit kommt erst danach und ginge
> natürlich genauso auch ohne Cast:
> int8_t i = 100 + 100;

Laut Standrad erzeugt dies jedoch implementation-defined behavior (so 
oder so, auch mit Cast):
> The result of, or the signal raised by, converting an integer to a
> signed integer type when the value cannot be represented in an object of
> that type

A. K. schrieb:
> Ok, also
>   if ((uint8_t)(a + b) > 100)
> Das darf der Compiler ohne Cast nicht in 8 Bits rechnen. Mit schon.

Bei unsigned ja. Da ist alles schön definiert und das Resultat ist in 
beiden Fällen (Berechnung in 8 Bit vs. Berechnung in int und 
anschliessender Cast) identisch. Bei signed sieht das aber anders aus. 
Wird die Berechnung in 8 Bit durchgeführt, kann aufgrund eines 
Überlaufes undefiniertes Verhalten auftreten. Rechnet er in int und 
castet dann in einen kleineren signed Typen, muss das Resultat in den 
kleineren Typen passen, da ansonsten implementierungsabhängiges 
Verhalten auftritt. Jedenfalls kann dadurch das Ergebnis der beiden 
Varianten nicht als identisch betrachtet werden. Der Compiler darf in 
diesem Fall nicht optimieren sondern muss in int rechnen und 
anschliessend casten.

von Rolf M. (rmagnus)


Lesenswert?

A. K. schrieb:
> Ok, also
>   if ((uint8_t)(a + b) > 100)
> Das darf der Compiler ohne Cast nicht in 8 Bits rechnen. Mit schon.

Ja. Prinzipiell ist hier zwar auch weiterhin die integer promotion 
durchzuführen, aber da es am Ergebnis nichts ändert, darf er als 
Optimierung  (entsprechend der "as-if rule") hier die Erweiterung auf 
int auch weglassen und alles in 8 Bit rechnen.

von (prx) A. K. (prx)


Lesenswert?

be s. schrieb:
> Wird die Berechnung in 8 Bit durchgeführt, kann aufgrund eines
> Überlaufes undefiniertes Verhalten auftreten.

Dass im C Standard der Überlauf bei vorzeichenbehafteter Rechnung 
undefiniert ist heisst nicht, dass es auch bei der realen Zielmaschine 
undefiniert wäre. Das Verhalten der Zielmaschine ist dem Compiler 
vielmehr bekannt. Er weiss also, dass bei einer 8-Bit Rechnung das 
gleiche Ergebnis herauskommt wie bei einer nach allen Regeln des 
Standards durchgeführten 16-Bit Rechnung.

> Der Compiler darf in diesem Fall nicht optimieren sondern muss in
> int rechnen und anschliessend casten.

Doch, er darf. Und genau das tut er auch.

Anders wäre es beispielsweise, wenn die Zielmaschine beim Befehl zur 
8-Bit Addition bei Überlauf einen Trap auslösen würde, beim Downcast mit 
Überlauf aber immer 127 rauskäme. Dann wäre es nicht zulässig.

: Bearbeitet durch User
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.