www.mikrocontroller.net

Forum: Compiler & IDEs Vorzeichenbehandlung und Typcast


Autor: Klaus W. (Firma: privat) (texmex)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo!!
int16_t offset;
int16_t i_charge;

i_charge = ((((int32_t)(ADC - offset))*1330)/241);

Wenn ADC == 3 und offset == 7 kommt -31567 raus.

Das verstehe ich nicht ganz.

Aber mir ist sowieso grundsätzlich nicht wirklich klar wie der gcc beim 
Typecasting vorgeht. Insbesondere wenn dabei signed/unsigned im Spiel 
ist.

Wenn ich sowas schreibe...
(int32_t)(a - b)
... wird doch vermutlich die Operation (a-b) mit ihren natürlichen Typen 
durchgeführt (bzw. ggf. automatisch gecastet) und erst das Ergebnis in 
int32 umgewandelt, oder?

Oder was passiert z.b. hier:
int16_t result;
result = (int8_t)-8;
Das Ergebnis ist -8. Der Ausdruck (int8_t)-8 ist aber eigentlich nur 8 
bit lang. Vermutlich wird hier automatisch noch ein (int16_t) cast bei 
der Zuweisung an result durchgeführt?

Es scheint also dass der Compiler bei Zuweisung eines "kleineren" an 
einen "größeren" Typ die zusätzlichen Bits entsprechend der signedness 
des zieldatentyps erzeugt.

Andererseits:
uint16_t result;
result = (int8_t)-8;
printf( "%d\n", result );
sagt:
65528

Wenn (int8_t)-8 binär 11111000 repräsentiert, dann aber als unsigned 
interpretiert wird, müsste es doch eigentlich zu 0000000011111000 
verlängert werden?
Oder nimmt der Compiler hier Länge des Zieldatentyps, ignoriert aber 
seine Signedness und verwedet die der Quelle?

Muss ich daraus folgern, dass die Angabe der Signedness innerhalb eines 
Casts irrelevant ist?

Ich kapier grad garnix mehr :-).

viele Grüße!
Klaus

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Klaus W. schrieb:
>
> Wenn ADC == 3 und offset == 7 kommt -31567 raus.
>

Schon möglich.
Habs nicht nachgerechnet.


> Aber mir ist sowieso grundsätzlich nicht wirklich klar wie der gcc beim
> Typecasting vorgeht.

Nach den C-Regeln:

Welches ist der Datentyp des linken Operanden einer Operation?
Welches ist der Datentyp des rechtne Operanden einer Operation?
Sind beide gleich oder muss einer an den anderen angepasst werden?
Danach wird dann die Operation ausgewählt
Die Operation liefert dann selbst wieder einen Datentyp (der 
normalerweise identisch ist, mit dem Datentyp auf den die beiden 
Operanden gebracht wurden)

Und so gehts dann weiter, den arithemtischen Punkt vor Strich Regeln 
nach und natürlich unter Berücksichtigung von zusätzlichen Klammerungen. 
Von innen nach aussen, von links nach rechts.

Daher ist ein beliebter Fehler, den Datentyp des Ergebnisses umzucasten 
(zb von int nach double) und sich zu wundern, warum zb eine Division in 
int gemacht wird, wo doch das Ergebnis auf double gecastet wurde.
Zum Zeitpunkt an dem die Entscheidung fällt, welche Operation genommen 
wird, ist der Datentyp des Ergebnisses sowas von uninteressant.

> Insbesondere wenn dabei signed/unsigned im Spiel
> ist.

Wenn einer der beiden Operanden unsigned ist, wird auch der andere zu 
unsigned.
Oder kurz gesagt: bei signed/unsigned gewinnt immer unsigned.

> Wenn ich sowas schreibe...
>
> (int32_t)(a - b)
> 
> ... wird doch vermutlich die Operation (a-b) mit ihren natürlichen Typen
> durchgeführt (bzw. ggf. automatisch gecastet)

Nö. Mit dem Datentyp der dem höherwertigen Datentyp von a bzw b 
entspricht.
int - long - double

sind a und b vom Typ char, wird die Operation in int gemacht. Ein 
Optimizer kann das noch verändern, aber grundsätzlich wird nicht kleiner 
als int gerechnet.

> und erst das Ergebnis in
> int32 umgewandelt, oder?

Yep.
Für die Auswahl des - Operators ist es unerheblich ob das Ergebnis davon 
in int32 umgewandelt wird.

> Oder was passiert z.b. hier:
>
> int16_t result;
> result = (int8_t)-8;
> 
> Das Ergebnis ist -8. Der Ausdruck (int8_t)-8 ist aber eigentlich nur 8
> bit lang. Vermutlich wird hier automatisch noch ein (int16_t) cast bei
> der Zuweisung an result durchgeführt?

Der Compiler kann immer von sich aus in einen 'höheren' Datentyp casten. 
Eine kleine Zahl kann mit einem 'größeren' Datenytp immer dargestellt 
werden.
Aber umgekehrt geht das nicht. 465 passt nun mal nicht in 8 Bit. Daher 
warnen gute Compiler an dieser Stelle. Die Warnung wird man nur dann 
los, wenn man explizit, mit einem Cast, sagt: Ist schon ok.

>
> uint16_t result;
> result = (int8_t)-8;
> printf( "%d\n", result );
> 
> sagt:
> 65528

Das glaub ich nicht. %d verlangt einen int in der Argumentliste. Du hast 
keinen int, du hast einen unsigned int.

> Oder nimmt der Compiler hier Länge des Zieldatentyps, ignoriert aber
> seine Signedness und verwedet die der Quelle?

Bei signed/unsigned Fragen, wird die Bitdarstellung nicht verändert. 
Intern ändert sich am Bitmuster gar nichts. Lediglich die Interpretation 
während einer Berechnung (und damit auch die Auswahl der Operation) 
verändert sich.

In dieser Operation
result = (int8_t)-8;
muss der Compiler 2 Dinge tun.
* die Bitlängen anpassen
* signed/unsigned anpassen

Zuerst wird die Bitlänge angepasst.
Dann wird signed/unsigned angepasst (welches keine auszuführende 
Operation benötigt - das Bitmuster bleibt das Gleiche).

Dein Bitmuster für -8 lautet  11111000
wird also zunächst auf 16 Bit aufgeblasen. Und da es sich hier um einen 
signed int handelt, wird das Vorzeichenbit natürlich dupliziert, denn 
der signed Zahlenwert darf sich ja nicht ändern, nur weil die Bitlänge 
größer wird.
Aus 11111000 wird so 1111111111111000
Als unsigned Zahl ist das dann 65528

Autor: Klaus W. (Firma: privat) (texmex)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Uff, vielen Dank erstmal!
Ich muss mir das alles nochmal durch den Kopf gehen lassen...

aber:

Karl heinz Buchegger schrieb:
> Klaus W. schrieb:

> In dieser Operation
> result = (int8_t)-8;
> muss der Compiler 2 Dinge tun.
> * die Bitlängen anpassen
> * signed/unsigned anpassen
>
> Zuerst wird die Bitlänge angepasst.
> Dann wird signed/unsigned angepasst (welches keine auszuführende
> Operation benötigt - das Bitmuster bleibt das Gleiche).
>
> Dein Bitmuster für -8 lautet  11111000
> wird also zunächst auf 16 Bit aufgeblasen. Und da es sich hier um einen
> signed int handelt, wird das Vorzeichenbit natürlich dupliziert, denn
> der signed Zahlenwert darf sich ja nicht ändern, nur weil die Bitlänge
> größer wird.
> Aus 11111000 wird so 1111111111111000
> Als unsigned Zahl ist das dann 65528

Ich schließe daraus, dass meine bisherige Vorstellung, dass Signedness 
und Länge eines Objekts quasi eine untrennbare Einheit beim Casten 
bilden falsch ist.
Dass signed/unsigned das Bitmuster nicht ändert, gilt ja nur, wenn nicht 
gleichzeitig eine Bitlängenänderung stattfindet, oder?

Der Hinweis, dass bei einer Operation immer unsigned gewinnt war schon 
mal recht erhellend.
Moment, das bedeutet ja eigentlich, dass die Berechnung eines wie auch 
immer gearteten Terms komplett unsigned durchgeführt wird, sobald auch 
nur EIN unsigned objekt enthalten ist?

Autor: Stefan Ernst (sternst)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Klaus W. schrieb:

> Der Hinweis, dass bei einer Operation immer unsigned gewinnt war schon
> mal recht erhellend.
> Moment, das bedeutet ja eigentlich, dass die Berechnung eines wie auch
> immer gearteten Terms komplett unsigned durchgeführt wird, sobald auch
> nur EIN unsigned objekt enthalten ist?

Nein, denn erstens gilt die "Typangleichung" der beteiligten Operanden 
nicht für jeden Operator, und zum anderen gibt es ja auch noch Klammern, 
so dass Teilterme durchaus signed gerechnet werden können.

Autor: Klaus W. (Firma: privat) (texmex)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Stefan Ernst schrieb:
> Klaus W. schrieb:
>
>> Der Hinweis, dass bei einer Operation immer unsigned gewinnt war schon
>> mal recht erhellend.
>> Moment, das bedeutet ja eigentlich, dass die Berechnung eines wie auch
>> immer gearteten Terms komplett unsigned durchgeführt wird, sobald auch
>> nur EIN unsigned objekt enthalten ist?
>
> Nein, denn erstens gilt die "Typangleichung" der beteiligten Operanden
> nicht für jeden Operator, und zum anderen gibt es ja auch noch Klammern,
> so dass Teilterme durchaus signed gerechnet werden können.

Uff, das scheint reichlich kompliziert...

Hm, mit den Klammern hast Du schon recht, aber zumindest das Ergebnis 
müsste dann immer unsigned sein.

Ist aber auch nicht, denn wenn ich Karl Heinz richtig verstanden habe, 
müsste ja zumindest
  result = (uint8_t)-8 - (uint8_t)4;

und
  result = (int8_t)-8 - (uint8_t)4;

identisch sein.

Im ersten Fall kommt aber 244 und im zweiten -12 raus.

Was ja auch irgendwie Sinn macht.

Autor: Stefan Ernst (sternst)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Klaus W. schrieb:

> Im ersten Fall kommt aber 244 und im zweiten -12 raus.

244 und -12 haben das selbe 8-Bit-Muster. ;-)

Autor: Klaus W. (Firma: privat) (texmex)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Stefan Ernst schrieb:
> Klaus W. schrieb:
>
>> Im ersten Fall kommt aber 244 und im zweiten -12 raus.
>
> 244 und -12 haben das selbe 8-Bit-Muster. ;-)

Stimmt, aber "result" ist hier wie in den Experimenten oben eine 16 bit 
Variable.

Autor: Stefan Ernst (sternst)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
In deinem Beispiel kommt das "unsigned gewinnt" auch gar nicht zur 
Anwendung, da ja der Ausgangswert auf beiden Seiten in ein signed int 
passt.

Autor: H. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> Dass signed/unsigned das Bitmuster nicht ändert, gilt ja nur, wenn nicht
> gleichzeitig eine Bitlängenänderung stattfindet, oder?
Bei einem Cast ändert sich das Bitmuster überhaupt nicht!

Autor: Klaus W. (Firma: privat) (texmex)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Also jetzt hab ich bald nen Knoten im Hirn.

Ich versuche das nochmal zu analysieren:

1. Fall:
  result = (uint8_t)-8 - (uint8_t)4;

Ich weiss eigentlich schon garnicht welche Bitlänge eine "blanke" 
Konstante wie "4" oder "-8" hat. Ist hier aber vermutlich nicht 
relevant.

(uint8_t) macht aus -8 -> 11111000 (bezüglich der länge) und sagt dem 
Compiler dass es als unsigned zu interpretieren ist.
Mit "4" passiert das gleiche -> 00000100.
Bei der Subtraktion entsteht 11110100. Also -12 oder 244. Je nach 
Interpretation. Ich vermute aber der Compiler geht als unsigned damit um 
weil die Operanden auch unsigned waren.

Da result 16 bit ist, wird also mit 0 erweitert -> 0000000011110100.
Was als signed interpretiert 244 ergibt.


2. Fall:
  result = (int8_t)-8 - (uint8_t)4;

-8 wird ebenfalls zu 11111000 aber als signed interpretiert.

Wäre es nun so, dass bei der Subtraktion beide Operanden als unsigned 
interpretiert werden, wenn einer von ihnen unsigned ist, müsste der 
weitere Ablauf dem Fall 1 entsprechen.

Tatsächlich ist aber eher das Gegenteil der Fall und die Operation wird 
komplett als signed ausgeführt. Demnach wird auch das Ergebnis als 
signed behandelt, weswegen bei der Erweiterung zu 16 bit 
1111111111110100 entsteht.

D.h. also für die Art der Erweiterung auf 16bit (signed/unsigned) bei 
der Zuweisung scheint die "rechte Seite" verantwortlich zu sein.

Aber (um zu dem anfänglichen Beispiel zurückzukehren)
int16_t offset;
int16_t i_charge;

i_charge = ((((int32_t)(ADC - offset))*1330)/241);

Ich bin mir nicht ganz sicher, welche Eigenschaften ADC eigentlich hat, 
vermute aber dass es unsigned ist. Daher modifizieren ich das zur 
Überprüfung nochmal so:
int16_t offset;
int16_t i_charge;
uint16_t adc;

offset=7;
adc=4;
i_charge = ((((int32_t)(adc - offset))*1330)/241);

Das Ergebnis ist wieder -31561.

Jetzt ist die Frage, ob signed oder unsigned bei der Erweiterung des 
Ergebnisses des Ausdrucks (adc-offset) auf 32 bit zugrunde gelegt wird.
Aus den obigen Experimenten schließe ich, dass es hier völlig wurscht 
ist, ob ich int32 oder uint32 schreibe, da die signedness der "rechten 
Seite" relevant ist. Ich könnte mir denken dass hier "signed" verwendet 
wird
(Wieder wegen der Ergebnisse obiger Experimente).
Also 4-7 = -3, in 16 bit: 1111111111111101. Erweitert zu 32 bit:
1111111111111111111111111111101. *1330 /241 = -16
In 32 bit also 1111111111111111111111111110000. Die Wandlung in 16bit
macht daraus 1111111111110000. Das könnte man jetzt als -16 oder
65520 interpretieren, aber niemals als -31561.

Der "Fehler" muss also irgendwo weiter "innen" bei der Berechnung 
passieren.

Aber ich verstehe nicht wo.

Autor: Klaus W. (Firma: privat) (texmex)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
H. schrieb:
>> Dass signed/unsigned das Bitmuster nicht ändert, gilt ja nur, wenn nicht
>> gleichzeitig eine Bitlängenänderung stattfindet, oder?
> Bei einem Cast ändert sich das Bitmuster überhaupt nicht!

Hm, stimmt. Aber bei nachfolgenden Operationen oder Zuweisungen (was ja 
wohl auch nur eine Operation ist) durchaus schon, oder?

Autor: Klaus W. (Firma: privat) (texmex)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Noch merkwürdiger ist, dass wenn ich das gleich Programm auf dem
linux host compiliere:
main()
{
   uint16_t ADC;
   int16_t offset;
   int16_t result;

   ADC= 4;
   offset = 7;

   result = ((((int32_t)(ADC - offset))*1330)/241);
   printf( "%d\n", result );
}

erwartungsgemäß -16 rauskommt.

Das merkwürdige Ergebnis berechnet also nur der AVR.


Ich verstehe jetzt bald gar nichts mehr.

Autor: Stefan Ernst (sternst)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> 2. Fall:
>
>   result = (int8_t)-8 - (uint8_t)4;
>
> -8 wird ebenfalls zu 11111000 aber als signed interpretiert.
>
> Wäre es nun so, dass bei der Subtraktion beide Operanden als unsigned
> interpretiert werden, wenn einer von ihnen unsigned ist, müsste der
> weitere Ablauf dem Fall 1 entsprechen.

Was du anscheinend nicht bedacht hast, ist, dass nach deinen eigenen 
Casts ja noch die "Integer Promotion Rules" zum Zuge kommen (und erst 
dort kommt dann auch das "unsigned gewinnt"). Da aber die Ausgangswerte 
alle in ein signed int passen, werden alle Operanden (in beiden Fällen) 
nach signed int promotet (auch die uint8_t). Es gibt danach kein 
unsigned mehr, das "gewinnen" könnte.
Aus Fall 1 wird:
(signed int) 248 - (signed int) 4
Aus Fall 2 wird:
(signed int) -8 - (signed int) 4

Autor: Stefan Ernst (sternst)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> Das merkwürdige Ergebnis berechnet also nur der AVR.

Weil auf dem Linux-System ein int größer als 16 Bit ist. Da werden bei 
(adc - offset) die beiden Operanden auch erst jeweils zu einem signed 
int promotet. Beim AVR sind die beiden Operanden bereits auf int-Größe 
und werden daher nur nach unsigned promotet.

Auf Linux-System:
(int32_t)(ADC - offset) = -3

Auf AVR:
(int32_t)(ADC - offset) = 65533

Autor: Klaus W. (Firma: privat) (texmex)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Stefan Ernst schrieb:

> Was du anscheinend nicht bedacht hast, ist, dass nach deinen eigenen
> Casts ja noch die "Integer Promotion Rules" zum Zuge kommen (und erst

Ja, anscheinend....

Seufz

:-)

Autor: Klaus W. (Firma: privat) (texmex)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Stefan Ernst schrieb:

> dort kommt dann auch das "unsigned gewinnt"). Da aber die Ausgangswerte
> alle in ein signed int passen, werden alle Operanden (in beiden Fällen)
> nach signed int promotet (auch die uint8_t). Es gibt danach kein
> unsigned mehr, das "gewinnen" könnte.

Ja, aber moment, das weiss man ja nur in meinem Testprogramm!
Sonst müsste das ja zur Laufzeit entschieden werden?

Autor: Stefan Ernst (sternst)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Klaus W. schrieb:

> Ja, aber moment, das weiss man ja nur in meinem Testprogramm!
> Sonst müsste das ja zur Laufzeit entschieden werden?

Warum? Es geht doch um den Typ, nicht den konkreten Inhalt. Es steht 
doch immer zur Compilezeit fest, welchen Typ die Beteiligten haben, und 
ob alle möglichen Werte diesen Typs durch ein signed int darstellbar 
sind. In deinem Beispiel sind es uint8_t und int8_t. Egal was da konkret 
drinsteht, es ist auf jeden Fall mit einem signed int darstellbar.

Autor: Klaus W. (Firma: privat) (texmex)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Stefan Ernst schrieb:
>> Das merkwürdige Ergebnis berechnet also nur der AVR.
>
> Weil auf dem Linux-System ein int größer als 16 Bit ist. Da werden bei

Auch wenn ich die Variable ausdrücklich mit int16_t definiere?
Hm, weil es ein 32 Bit System ist und die übrigen 16bit trotzdem 
irgendwie da sind? Dann ist das also eher so eine Art 
Mindestanforderung?
Da muss man bei irgendwelchen Bit-Shifts ja direkt auch aufpassen.

> (adc - offset) die beiden Operanden auch erst jeweils zu einem signed
> int promotet. Beim AVR sind die beiden Operanden bereits auf int-Größe
> und werden daher nur nach unsigned promotet.

Das hab ich nicht ganz kapiert. Also auf Linux rechnet die cpu obwohl 
ich int16_t definiert habe mit 32 bit. Da muss aber doch dann 
längenmäßig auch nichts mehr promoted werden, weil das dann doch schon 
die ganze zeit 32 bit ist?


> Auf Linux-System:
> (int32_t)(ADC - offset) = -3
>
> Auf AVR:
> (int32_t)(ADC - offset) = 65533

Was ja auch wieder das gleiche 16-bit-muster ist, welches durch das 
(int32_t) im beispiel:

[c]
offset=7;
adc=4;
i_charge = ((((int32_t)(adc - offset))*1330)/241);
[\c]

für die weiteren Operationen (*1330, /241) dann doch wieder als -3 
interpretiert werden müsste?

Oder haben da die Promotion Rules noch eine höhere Priorität als das 
cast obwohl es (außerhalb der klammer) erst danach kommt?

Rätsel über Rätsel...

Autor: Klaus W. (Firma: privat) (texmex)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Stefan Ernst schrieb:
> Klaus W. schrieb:
>
>> Ja, aber moment, das weiss man ja nur in meinem Testprogramm!
>> Sonst müsste das ja zur Laufzeit entschieden werden?
>
> Warum? Es geht doch um den Typ, nicht den konkreten Inhalt. Es steht
> doch immer zur Compilezeit fest, welchen Typ die Beteiligten haben, und
> ob alle möglichen Werte diesen Typs durch ein signed int darstellbar
> sind. In deinem Beispiel sind es uint8_t und int8_t. Egal was da konkret
> drinsteht, es ist auf jeden Fall mit einem signed int darstellbar.

Hm, viellicht müsste man erst mal klären was man unter "darstellbar" 
versteht.

240 z.b. kann ich mit einem vorzeichenlosen integer der länge 8 bit 
darstellen. mit einem vorzeichenbehafteten integer der länge 8 bit aber 
nicht.
Oder sehe ich da was falsch?

Autor: Stefan Ernst (sternst)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Klaus W. schrieb:

> Auch wenn ich die Variable ausdrücklich mit int16_t definiere?

> Das hab ich nicht ganz kapiert. Also auf Linux rechnet die cpu obwohl
> ich int16_t definiert habe mit 32 bit.

Das ist Bestandteil der "Integer Promotion Rules". Die Operanden haben 
mindestens die Größe eines ints. Notfalls werden sie vor der Berechnung 
eben entsprechend erweitert.

>> Auf AVR:
>> (int32_t)(ADC - offset) = 65533
>
> Was ja auch wieder das gleiche 16-bit-muster ist, welches durch das
> (int32_t) im beispiel:
>
> [c]
> offset=7;
> adc=4;
> i_charge = ((((int32_t)(adc - offset))*1330)/241);
> [\c]
>
> für die weiteren Operationen (*1330, /241) dann doch wieder als -3
> interpretiert werden müsste?

Nein, denn das Ergebnis von (ADC - offset) ist unsigned. Also findet bei 
der Erweiterung auf int32_t keine Sign-Extension statt.

Autor: Stefan Ernst (sternst)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Klaus W. schrieb:

> Hm, viellicht müsste man erst mal klären was man unter "darstellbar"
> versteht.
>
> 240 z.b. kann ich mit einem vorzeichenlosen integer der länge 8 bit
> darstellen. mit einem vorzeichenbehafteten integer der länge 8 bit aber
> nicht.
> Oder sehe ich da was falsch?

Es geht aber nicht um einen "vorzeichenbehafteten integer der länge 8 
bit", sondern um ein "signed int", und der ist bei AVR nun mal 16 Bit 
groß.

PS: So langsam bekomme ich das Gefühl, dass wir uns im Kreis drehen.

Autor: Klaus W. (Firma: privat) (texmex)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> PS: So langsam bekomme ich das Gefühl, dass wir uns im Kreis drehen.

Ja, ich auch :-).

Ich werde jetzt erst mal über alles in Ruhe nachdenken.

Erstmal vielen Dank für Deine Mühe!

Autor: A. K. (prx)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Karl heinz Buchegger schrieb:

> Oder kurz gesagt: bei signed/unsigned gewinnt immer unsigned.

Nochmal kurzgefasst um Missverständnissen vorzubeugen: Das stimmt in 
dieser Form nur, wenn beide Seiten die gleiche Anzahl Bits haben, und 
diese mindestens der von int/unsigned entspricht.

Autor: Klaus W. (Firma: privat) (texmex)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo!

So, zwischenzeitlich habe ich mich nochmal etwas mit der Materie
beschäftigt.

@Stefan Ernst:
> PS: So langsam bekomme ich das Gefühl, dass wir uns im Kreis drehen.
Im Kreis nicht, aber ziemlich aneinander vorbei :-)

Natürlich will ich die Geduld von niemand überstrapazieren und ich
bitte meine Unwissenheit zu entschuldigen. :-)

Zumindest meine ich jetzt begriffen zu haben weshalb der Compiler im
eingangs erwähnten Beispiel zu diesem Ergebnis kommt. Also nochmal:
int16_t offset=7; // So war die ursprüngliche Definition, aber wie
                  // nun gelernt, ist int sowieso gleich int16_t auf
                  // AVR.
uint16_t adc=4;
int16_t i_charge;
i_charge = ((((int32_t)(adc - offset))*1330)/241);

Zuerst wird also (adc - offset) bearbeitet.

Nachdem es hier keine expliziten Casts gibt, kommen die Promotion Rules
zum Einsatz. Auf der Suche nach einer entsprechenden Referenz bin ich
auf ein PDF mit dem Titel "Programming languages - C, Committee Draft
August 3, 1998 WG14/N843" gestoßen.
Gibt es davon inzwischen auch eine Final Version? Wenn ich richtig
verstehe, ist C9x ein ISO Standard und wenn man so umgangssprachlich von
ANSI-C spricht, stimmt das gar nicht wirklich?
Gibt es eigentlich offizielle Quellen für derartige Referenzdokumente?
Ich schätze, wenn man ernsthaft C programmieren will, kommt man nicht
drum rum sich damit auseinander zu setzen.

However... darin findet sich folgende Aussage, die in meinem Fall
zutreffen dürfte:
   "Otherwise, if the operand that has unsigned integer type has rank
greater or equal to the rank of the type of the other operand,
then the operand with signed integer type is converted to the type
of the operand with unsigned integer type."

Ich nehme an das ist es, was Karl Heinz mit
"Oder kurz gesagt: bei signed/unsigned gewinnt immer unsigned."
gemeint hat.

(adc - offset) wird demnach zu unsigned int (hier 16 bit).
Nun kommt mein (int32_t) cast.
Hier wird aber offenbar ZUERST in 32bit gewandelt und erst DANN
signed drauss gemacht. Weswegen die neu hinzgekommenen 16 bit
mit "0" und nicht mit "1" aufgefüllt werden.

Also das was Stefan Ernst mit

" Also findet bei der Erweiterung auf int32_t keine Sign-Extension
statt." gemeint hat.

Naja, der Rest ist klar (denke ich jedenfalls): aus der kleinen
negativen Zahl wird eine recht große Positive, die nach der
Multiplikation noch größer wird und deren Ergebnis schließlich nicht
mehr in die 16 bit Variable i_charge passt.
Ich vermute da werden die höherwertigen Bits einfach abgeschnitten und
es kommt irgend ein Unsinn dabei raus?


Ok, so weit so klar. Trotzdem würde ich die ganze Thematik gerne noch
etwas abstrakter zu fassen bekommen.

Also erstmal zu den integer Typen grundsätzlich:

Ich fand die Bitlängen Zuordnungen in C schon immer etwas verwirrend
(ist es vermutlich auch) und habe mich daher in der Mikrocontroller
Programmierung auf die (u)int(8/16/..)_t Typen "verlassen" in der
Meinung damit quasi feste Bitlängen zu bekommen.

Mal einen Blick in stdint.h werfen:
Daraus kann man für den AVR folgendes destillieren:
typedef signed char int8_t;
typedef unsigned char uint8_t;
typedef signed int int16_t;
typedef unsigned int uint16_t;
typedef signed long int int32_t;
typedef unsigned long int uint32_t;
typedef signed long long int int64_t;
typedef unsigned long long int uint64_t;

Ist stdint.h eigentlich erst eine Neuerscheinung von C99?

Unter Linux wo int 32bit lang ist (vermutlich einfach wegen der
intel Achitektur), müsste stdint.h dementsprechend anders aussehen.

Ja, tatsächlich:
typedef signed char             int8_t;
typedef short int               int16_t;
typedef int                     int32_t;
typedef long int                int64_t;
typedef long long int           int64_t;
typedef unsigned char           uint8_t;
typedef unsigned short int      uint16_t;
typedef unsigned int            uint32_t;
typedef unsigned long int       uint64_t;
typedef unsigned long long int  uint64_t;

Verstehe ich aber richtig, dass die neuen C99 Integer Typen eigentlich
auch wieder nur Mindestanforderungen sind? Wenn ein signed char
(= int8_t) in einem Intel-Register steht, wird das auch 32Bit
beanspruchen?

Ok, zurück zu den Casts. Interessanter Weise funktioniert
das obige Beispiel z.B. wenn man es so modifiziert:
i_charge = ((((int32_t)(int16_t)(adc - offset))*1330)/241);

Hier wird also aus dem uint (adc - offset) erstmal wieder int
und dann bei der Erweiterung auf 32bit auch korrekt mit "1"
gefüllt.

Am sinnvollsten wäre vermutlich zu schreiben:
i_charge = ((int32_t)((int16_t)ADC - offset)*1330)/241;

Trotzdem würde ich die Verwendung der Casts durch den Compiler gerne
irgendwie in allgemeingültigen Regeln formulieren.

* (casts) haben eine höhere Priorität als die Promotion Rules?
  D.h. die Promotion Rules werden erst auf das Ergebnis des
  Casts angewendet?
* Bei einem (cast) wird zuerst die Bitlänge entsprechend
  der signedness des ursprünglichen Datentyps angepasst und
  DANN das neue (signed/unsigned) Label "draufgeklebt"?
* Heisst das, wenn ich mittels Cast Bitlänge und
  Signedness gleichzeitig verändern will, muss ich das
  immer mit zwei casts machen?
  Also erstmal korrekte signedness "einstellen", damit die anschließende
  Bitlängenanpassung mit korrektem Vorzeichen erfolgt?
* In mancher C-Literatur ist von impliziter Typanpassung die Rede.
  Ich nehme an damit sind eigentlich die Promotion Rules gemeint?




H. (Gast) schreibt:
"Bei einem Cast ändert sich das Bitmuster überhaupt nicht!"

Stimmt das so pauschal wirklich?

Ich habe mir einen Cast bisher immer so als unären Operator vorgestellt.
Sofern ich nur signed in unsigned oder umgekehrt caste, ändert sich
natürlich nichts an dem Bitmuster.
Wenn sich die Bitlänge verändert, ändert sich damit aber doch auch
das Bitmuster. Falls dabei gleichzeitig noch signed/unsigned
im Spiel ist, z.T. doch recht erheblich?


Kann es sein dass ich grundsätzlich irgend was falsch mache,
wenn ich mir über diese Dinge so viele Gedanken mache(n muss)?


Viele Grüße!
Klaus

Autor: Stefan Ernst (sternst)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Klaus W. schrieb:

> Nachdem es hier keine expliziten Casts gibt, kommen die Promotion Rules
> zum Einsatz.

Nein. Die Promotion Rules kommen immer zum Einsatz. Ein expliziter Cast 
ändert nur die Ausgangsbasis. Man könnte es auch so betrachten, dass du 
mit dem Cast einen "Zwischenschritt" einschiebst.

> Wenn ich richtig
> verstehe, ist C9x ein ISO Standard und wenn man so umgangssprachlich von
> ANSI-C spricht, stimmt das gar nicht wirklich?

ANSI-C ist einfach nur ein älterer Standard.

> Gibt es eigentlich offizielle Quellen für derartige Referenzdokumente?

http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1124.pdf

> * (casts) haben eine höhere Priorität als die Promotion Rules?
>   D.h. die Promotion Rules werden erst auf das Ergebnis des
>   Casts angewendet?
> * Bei einem (cast) wird zuerst die Bitlänge entsprechend
>   der signedness des ursprünglichen Datentyps angepasst und
>   DANN das neue (signed/unsigned) Label "draufgeklebt"?
> * Heisst das, wenn ich mittels Cast Bitlänge und
>   Signedness gleichzeitig verändern will, muss ich das
>   immer mit zwei casts machen?
>   Also erstmal korrekte signedness "einstellen", damit die anschließende
>   Bitlängenanpassung mit korrektem Vorzeichen erfolgt?
> * In mancher C-Literatur ist von impliziter Typanpassung die Rede.
>   Ich nehme an damit sind eigentlich die Promotion Rules gemeint?

Man könnte vielleicht an dem einen oder anderen Detail deiner Regeln 
rummäkeln (oder das eine oder andere Detail ergänzen), aber ich denke 
vom Prinzip her hast du es verstanden.

> H. (Gast) schreibt:
> "Bei einem Cast ändert sich das Bitmuster überhaupt nicht!"
>
> Stimmt das so pauschal wirklich?

Nein. Hängt natürlich auch davon ab, auf was genau sich das "ändern" 
bezieht. Z.B. bei einer Erweiterung von 8 auf 16 Bit kann sich das 
"Gesamt-Bitmuster" natürlich ändern, wobei aber das Bitmuster der 
ursprünglichen 8 Bit sich nicht ändert.
Und wenn Floats ins Spiel kommen, stimmt die Aussage gar nicht mehr.

> Kann es sein dass ich grundsätzlich irgend was falsch mache,
> wenn ich mir über diese Dinge so viele Gedanken mache(n muss)?

Nein. Das Nichtbeachten/Nichtverstehen der Promotion Rules ist nicht 
selten der Grund, wenn etwas nicht so läuft, wie man denkt dass es 
laufen müsste.

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Klaus W. schrieb:

> Nachdem es hier keine expliziten Casts gibt, kommen die Promotion Rules
> zum Einsatz. Auf der Suche nach einer entsprechenden Referenz bin ich
> auf ein PDF mit dem Titel "Programming languages - C, Committee Draft
> August 3, 1998 WG14/N843" gestoßen.
> Gibt es davon inzwischen auch eine Final Version?

Natürlich, die muss man aber kaufen :-)

> Ich schätze, wenn man ernsthaft C programmieren will, kommt man nicht
> drum rum sich damit auseinander zu setzen.

Nein, eher nicht.
Diese Dokumente sind gut für Compilerbauer und wenn es darum geht 
Streitfragen auf 'alademischen Niveau' zu entscheiden. Als normaler, 
durchaus guter Programmierer, benötigt man diese Dokumente eigentlich so 
gut wie nie. Die Information darin, ist zu verstreut und oftmals auch so 
richtig 'juristisch' verpackt, dass man schon Übung haben muss, um das 
Lesen zu können.
Mit einer der üblichen Standard-Literatur-Quellen ist man besser 
bedient.

>    "Otherwise, if the operand that has unsigned integer type has rank
> greater or equal to the rank of the type of the other operand,
> then the operand with signed integer type is converted to the type
> of the operand with unsigned integer type."
>
> Ich nehme an das ist es, was Karl Heinz mit
> "Oder kurz gesagt: bei signed/unsigned gewinnt immer unsigned."
> gemeint hat.

Yep.
Übrigens ein gutes Beispiel für die 'juristische Schreibweise'.

> (adc - offset) wird demnach zu unsigned int (hier 16 bit).

Genauer:
  adc     ist bereits ein unsigned int
  offset  wird zu einem unsigned umgepfriemelt

Dann wird die Subtraktion gemacht
Und natürlich hat auch das Ergebnis der Subtraktion den Typ unsigned int

> Nun kommt mein (int32_t) cast.
> Hier wird aber offenbar ZUERST in 32bit gewandelt und erst DANN
> signed drauss gemacht.

Genau.

> Weswegen die neu hinzgekommenen 16 bit
> mit "0" und nicht mit "1" aufgefüllt werden.
>
> Also das was Stefan Ernst mit
>
> " Also findet bei der Erweiterung auf int32_t keine Sign-Extension
> statt." gemeint hat.
>

Ganz genau

> Naja, der Rest ist klar (denke ich jedenfalls): aus der kleinen
> negativen Zahl

welche negative Zahl. Da ist keine negative Zahl. Das Ergebnis von adc - 
offset ist Kraft Definition immer positiv, da unsigned.
Allerdings gibt es bei der Subtraktion einen Unterlauf. Und wie der im 
Falle unsigned gehandhabt wird, steht auch im Standard: Es wird Modulo 
Arithmetik betrieben. Und so kommt es, dass bei dieser Subtraktion eine 
große positive Zahl herauskommt.

> wird eine recht große Positive, die nach der
> Multiplikation noch größer wird und deren Ergebnis schließlich nicht
> mehr in die 16 bit Variable i_charge passt.

Das könnte hinkommen (müsste man jetzt nachrechnen).

> Ich vermute da werden die höherwertigen Bits einfach abgeschnitten und
> es kommt irgend ein Unsinn dabei raus?

Ganz genau.

> Ich fand die Bitlängen Zuordnungen in C schon immer etwas verwirrend
> (ist es vermutlich auch) und habe mich daher in der Mikrocontroller
> Programmierung auf die (u)int(8/16/..)_t Typen "verlassen" in der
> Meinung damit quasi feste Bitlängen zu bekommen.

Das ist auch so.

> Ist stdint.h eigentlich erst eine Neuerscheinung von C99?

Ja. Nach langem Bitten und Betteln der Cummunity

> Verstehe ich aber richtig, dass die neuen C99 Integer Typen eigentlich
> auch wieder nur Mindestanforderungen sind?

Nein, das verstehst du falsch.
In dem Fall gilt tatächlich: Drinn ist, was drauf steht.

> Wenn ein signed char
> (= int8_t) in einem Intel-Register steht, wird das auch 32Bit
> beanspruchen?

Na, ja. Das ist klar. In einem Register hat natürlich dort jeder Wert 32 
Bit, weils anders nicht geht. Aber C kennt keine CPU Register. Die 
Bitlänge interessiert hier nur beim Speichern und Laden im RAM. Alles 
andere kann der Compiler (fast) machen wie er will, solange er sich an 
die Verarbeitungsregeln hält.

> H. (Gast) schreibt:
> "Bei einem Cast ändert sich das Bitmuster überhaupt nicht!"
>
> Stimmt das so pauschal wirklich?

Nein.
Nur in diesem speziellen signed/unsigned Fall, wenn die Bitlängen schon 
stimmen.

> Kann es sein dass ich grundsätzlich irgend was falsch mache,
> wenn ich mir über diese Dinge so viele Gedanken mache(n muss)?

Das kann sein.
Generell solltest du nach der Devise verfahren:
Wenn du viele Casts verwenden musst, dann zurücklehnen, überlegen warum 
das so ist und die Datentypen aller Variablen noch mal auf 
SInnhaftigkeit durchgehen.
Ein Cast ist eine Waffe! Damit hebelst du das Typsystem des Compilers 
aus. Daher: sparsam verwenden.

Autor: Stefan Ernst (sternst)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Eine Ergänzung hätte ich da noch :-)

> i_charge = ((((int32_t)(int16_t)(adc - offset))*1330)/241);
>
> Am sinnvollsten wäre vermutlich zu schreiben:
>
> i_charge = ((int32_t)((int16_t)ADC - offset)*1330)/241;

Ich würde folgendes für "am sinnvollsten" halten:
i_charge = (((int32_t)ADC - offset)*1330)/241;
Erstens ist das etwas übersichtlicher, und zweitens funktioniert das 
auch dann noch, wenn der ADC die 16 Bit voll nutzen würde.

Autor: Klaus W. (Firma: privat) (texmex)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
@Stefan Ernst:
> Nein. Die Promotion Rules kommen immer zum Einsatz. Ein expliziter Cast
> ändert nur die Ausgangsbasis. Man könnte es auch so betrachten, dass du
> mit dem Cast einen "Zwischenschritt" einschiebst.

Ja, so hatte ich das gemeint.

> http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1124.pdf
Danke!

> Und wenn Floats ins Spiel kommen, stimmt die Aussage gar nicht mehr.
Ja, das ist eh klar...


@ Karl heinz Buchegger
>> Gibt es davon inzwischen auch eine Final Version?
>Natürlich, die muss man aber kaufen :-)
Ja wie jetzt :-). Stefan hat doch oben einen Link gepostet?

> Mit einer der üblichen Standard-Literatur-Quellen ist man besser bedient.
Hm, also hier liegen auch so ein paar C-Schinken herum, aber diese 
Thematik habe ich bislang noch in keinem davon erschöpfend behandelt 
gefunden.


>Genauer:
>  adc     ist bereits ein unsigned int
>  offset  wird zu einem unsigned umgepfriemelt
>
>Dann wird die Subtraktion gemacht
>Und natürlich hat auch das Ergebnis der Subtraktion den Typ unsigned int
Richtig. So hatte ich das schon auch verstanden.

>> Naja, der Rest ist klar (denke ich jedenfalls): aus der kleinen
>> negativen Zahl
>welche negative Zahl. Da ist keine negative Zahl. Das Ergebnis von adc -
>offset ist Kraft Definition immer positiv, da unsigned.
Mhmm, jaaa, aber ICH sehe das Bitmuster trotzdem als "kleine negative 
Zahl".
>Allerdings gibt es bei der Subtraktion einen Unterlauf. Und wie der im
>Falle unsigned gehandhabt wird, steht auch im Standard: Es wird Modulo
>Arithmetik betrieben. Und so kommt es, dass bei dieser Subtraktion eine
>große positive Zahl herauskommt.
Ja, aber das Bitmuster ist doch wieder das gleiche?
Na, bevor wir jetzt Gefahr laufen aufgrund meiner mangelhaft präzisen 
Beschreibungen wieder aneinander vorbeireden :-). Ich denke ich hab's 
schon kapiert.


@Stefan:
> Ich würde folgendes für "am sinnvollsten" halten:
> i_charge = (((int32_t)ADC - offset)*1330)/241;
>
> Erstens ist das etwas übersichtlicher, und zweitens funktioniert das
>auch dann noch, wenn der ADC die 16 Bit voll nutzen würde.

Ja, das entspricht wohl auch der Philosophie von Karl Heinz.

Was ich allerdings eigentlich damit bezwecken wollte, war möglichst 
wenig Code im Flash zu erzeugen, aber eine höhere Genauigkeit zu 
erreichen als bei reiner 16bit Arithmetik. Daher dachte ich die Addition 
noch in 16 bit auszuführen, die Multiplikation in 32-bit und das 
Ergebnis möglichst schnell wieder auf 16 bit zu bringen.

viele Grüße,
Klaus

Autor: Stefan Ernst (sternst)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Klaus W. schrieb:
>>> Gibt es davon inzwischen auch eine Final Version?
>>Natürlich, die muss man aber kaufen :-)
> Ja wie jetzt :-). Stefan hat doch oben einen Link gepostet?

Naja, das ist ja auch "nur" ein Draft. Was "richtig offizielles" muss 
man wohl kaufen.

Autor: Klaus W. (Firma: privat) (texmex)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Stefan Ernst schrieb:
> Klaus W. schrieb:
>>>> Gibt es davon inzwischen auch eine Final Version?
>>>Natürlich, die muss man aber kaufen :-)
>> Ja wie jetzt :-). Stefan hat doch oben einen Link gepostet?
>
> Naja, das ist ja auch "nur" ein Draft. Was "richtig offizielles" muss
> man wohl kaufen.

Hm, ok, aber das ist ja schon fast offiziell...

"The lastest publically available version of the standard is the 
combined C99 + TC1 + TC2, WG14 N1124, dated 2005-05-06. This is a WG14 
working paper, but it reflects the consolidated standard at the time of 
issue."

Was dann darin fehlt ist

"Technical Corrigendum 3 (ISO/IEC 9899:1999 Cor. 3:2007(E)) was 
published in 2007"

Aber es heisst weiter:

"It can be obtained free of charge fro ISO. "

Autor: Jörg Wunsch (dl8dtl) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Klaus W. schrieb:

> Was dann darin fehlt ist
>
> "Technical Corrigendum 3 (ISO/IEC 9899:1999 Cor. 3:2007(E)) was
> published in 2007"

http://www.open-std.org/JTC1/SC22/wg14/www/docs/n1336.pdf

Autor: Caradhras (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Mahlzeit!

Ja, der Thread ist alt! Aber er hat mir gerade sehr geholfen, als ich 
mir bei einem ach so einfachen Teil meines Quellcodes stundenlang einen 
abgebrochen habe.

Ich sage nur:

int32_t ergebnis = 0;
ergebnis = (int32_t)(int16_t)"weitere operationen";

ergebnis = korrektes, vorzeichenbehaftetes ergebnis!

Da wäre ich nieeeee drauf gekommen und in meinem super dicken C-Buch 
steht auch nix dazu drin.

Also vielen Dank für diesen Thread! :)

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.