Forum: Mikrocontroller und Digitale Elektronik integer Verständnis


von elGasto (Gast)


Lesenswert?

Tag miteinander,

mal angenommen ich habe zwei Integer, mit denen ich eine Berechnung 
durchführen möchte. Darf dann ein Zwischenergebnis den Maximalwert eines 
Integers überschreiten?

Um mich deutlicher auszudrücken:
1
int16_t a = 521;
2
int16_t b = 732;
3
4
int16_t erg = (a * b)/100;

Es geht nicht darum, dass bei dem Ergebnis die Nachkommastellen 
abgeschnitten werden, sondern darum, ob nach der Multiplikation mit dem 
richtigen Zwischenergebnis weitergerechnet wird.

Wenn ja, dann warum?
Wenn nein, wie verhindere ich die Probleme am elegantesten?

Gruß

von Mmmh (Gast)


Lesenswert?

>Darf dann ein Zwischenergebnis den Maximalwert eines
>Integers überschreiten?
Ja.

>ob nach der Multiplikation mit dem
>richtigen Zwischenergebnis weitergerechnet wird
Nein.

>warum?
Hmmm. Na ein Integer ist nunmal nicht grösser als es gross ist. 16 Bit 
bleiben 16 Bit.

>wie verhindere ich die Probleme
Drei Alternativen.
1. Von vorne herein die Ausgangsvariablen so gross wählen, dass evtl. 
Zwischenergebnisse noch hineinpassen.
2. Rechnung so umstellen, das Zwischenergebnisse klein genug bleiben.
3. Variablen bzw. Ausdruck auf grösseren Typ casten (und ggf. das 
Ergebnis wieder zurück)

von Matthias L. (Gast)


Lesenswert?

>wie verhindere ich die Probleme

Zwinge den Compiler, die Berechnung mit einem größeren Datentypen 
durchzuführen:
1
int16_t erg;
2
erg = (int16_t)  (  (int32_t) a  *  (int32_t) b  );
3
erg /= 100;

von Mmmh (Gast)


Lesenswert?

Die Antwort von Matthias wäre dann Alternative Nr. 3 von mir.

von Sven P. (Gast)


Lesenswert?

Oder ordne (wenn möglich) die Rechenschritte anders an.
Ansonsten gäbe es da noch die Integerpromotion, die kleine Ints 
automatisch aufbläst. Dazu müsste man allerdings wissen, wie groß ein 
natürlicher Int auf der Plattform ist.

von nils (Gast)


Lesenswert?

>Es geht nicht darum, dass bei dem Ergebnis die Nachkommastellen
Dein Code kann sowieso nicht mit Kommas umgehen, nur ganzzahlen.

von Mmmh (Gast)


Lesenswert?

>Oder ordne (wenn möglich) die Rechenschritte anders an.
Das wäre Alternative Nr. 2 von mir.

Sich auf Integer Promotion zu "verlassen" um gültige Zwischenwerte zu 
erreichen ist von den anzustellenden Überlegungen her gleichwertig zu 
Nr. 3.

Das Problem damit ist, das die Ursache für den Vorgang im Compiler 
nichts mit Deinem Problem der Zwischenwerte zu tun hat. Ein Aspekt ist, 
das wenn Datentypen verwendet werden, die kleiner als der "typische" 
Maschinenwert sind, eine Erweiterung auf den nächstgrösseren Integer Typ 
erfolgt. Ob ein Operator in dem Zusammenhang einen Überlauf (oder 
Unterlauf) erzeugen könnte spielt dabei keine Rolle. Siehe K&R, 2. 
Auflage, S.189.
Der zweite Aspekt der damit unter "Integer Promotion" zusammengefasst 
wird, hängt nur vom Operator und den Typen der Operanden ab. Wieder 
nicht von der Frage ob ein Überlauf stattfindet (oder stattfinden 
könnte). Siehe K&R, 2. Auflage, S.191

Der wesentliche Zweck dieser "Integer Promotion" ist es, die notwendigen 
Implementierungen für die Operatoren zu verringern. Es werden die 
Datentypen vereinheitlicht. D.h. statt z.B. eine 
short-int-Multiplikation zu implementiereren wandelt man (das ist nur 
ein Beispiel) das short in ein int um.

Am besten wirst Du das als Anfänger mal lesen, aber ansonsten ignorieren 
und entweder durch entsprechende Typwahl oder casts die Überläufe 
verhindern.

von Simon K. (simon) Benutzerseite


Lesenswert?

Matthias Lipinsky schrieb:
>
1
> int16_t erg;
2
> erg = (int16_t)  (  (int32_t) a  *  (int32_t) b  );
3
> erg /= 100;
4
>

Ein mal (int32_t) reicht aber auch ;) Nur ein Operand muss nach int32_t 
gecastet werden, damit die Operation 32bittig ausgeführt wird.

von Matthias L. (Gast)


Lesenswert?

>Ein mal (int32_t) reicht aber auch ;) Nur ein Operand muss nach int32_t
>gecastet werden, damit die Operation 32bittig ausgeführt wird.

Ich weiß, aber das sieht schöner aus.


;-)

von Simon K. (simon) Benutzerseite


Lesenswert?

Lass ich durchgehen. Aber dein Code funktioniert trotzdem nicht. Du 
musst die Division durch 100 auch in 32 Bit ausführen :-) Bisher wird 
einfach nur in 32 Bit multipliziert und dann wieder auf 16 Bit gestutzt.

Richtig:
1
int16_t erg;
2
erg = (int16_t)  ( ( (int32_t) a  *  b ) / 100 );

von Mmmh (Gast)


Lesenswert?

>Bisher wird einfach nur in 32 Bit multipliziert
>und dann wieder auf 16 Bit gestutzt.
Nein. Wie kommst Du darauf? Am besten Verweis auf die entsprechende K&R 
Seite.

von Simon K. (simon) Benutzerseite


Lesenswert?

Mmmh schrieb:
>>Bisher wird einfach nur in 32 Bit multipliziert
>>und dann wieder auf 16 Bit gestutzt.
> Nein. Wie kommst Du darauf? Am besten Verweis auf die entsprechende K&R
> Seite.

Das würde mich stark wundern, wenn das nicht so wäre.
Die Zeile
1
erg /= 100
enthält keinen Cast nach 32 Bit, erg ist aber 16 Bit. Ergo wird die 
Division in 16 Bit ausgeführt.

von Mmmh (Gast)


Lesenswert?

Hast recht. Ich habe übersehen, das die Division jetzt in einem eigenen 
Ausdruck steht.

von Rumkugel (Gast)


Lesenswert?

Fängt das Problem nicht eigentlich schon eine Zeile früher an?
1
erg = (int16_t)  (  (int32_t) a  *  (int32_t) b  );
Hier wird das Zwischenergebnis auf 16 bit gecastet, obwohl es durchaus 
(wie z.B. in dem ursprünglichen Beispiel) größer als 16 bit sein kann.

von Jupp (Gast)


Lesenswert?

A:
1
erg = (int16_t)  (  (int32_t) a  *  (int32_t) b  );

Jo, sehe ich das auch.
Mit den Zahlen von oben:
1
int16_t a = 521; // 0x209
2
int16_t b = 732; // 0x2DC
3
4
int16_t erg = (a * b)/100;

Dieser Zwischencast auf 32 Bit bringt bei diesem Ausdruck nix, da ja 
danach wieder EXPLIZIT auf 16 Bit runtergecastet wird...

0x209 * 0x2DC = 0xD1BC.. (binaer: 1101 0001 1011 1100)
Naja in diesem Beispiel ist der Cast sowieso witzlos, da < (2^16)-1

Wie schon geschrieben funktioniert es so:
1
int16_t erg = (int16_t) ((int32_t(a * b)) / ((int32_t)100));

Hier hat man allerdings Nachteile in der Perfomance, da 32 Bit 
Operationen auf einer 16 oder 8 Bit Maschine natuerlich deutlich laenger 
dauern.

Deshalb versuche ich aus Performancegründen zuerst immer die maximal 
moeglichen Wertebereiche abzustecken und danach die Operationen darauf 
auszulegen..

gruss

von Klaus (Gast)


Lesenswert?

Jupp schrieb:
> Wie schon geschrieben funktioniert es so:
> int16_t erg = (int16_t) ((int32_t(a * b)) / ((int32_t)100));

Nein, so funktioniert es nicht.  bei int32_t(a * b) wird die 
multiplikation is 16 Bit ausgeführt und das Ergebnis in 32 Bit 
gecastet.

Und der cast vor der 100 ist unnötig, das der erste Operand des / schon 
32 Bit ist.

Also:
1
int16_t erg = (int16_t) ( (int32_t)a * b  / 100 );

von Simon K. (simon) Benutzerseite


Lesenswert?

Klaus schrieb:
> Also:
>
1
> int16_t erg = (int16_t) ( (int32_t)a * b  / 100 );
2
>

Siehe meine Version, ich habe nur geschickt Klammern eingefügt, damit 
einem sofort klar wird in welcher Reihenfolge die Operatoren 
abgearbeitet werden (Division zu letzt). Dafür nehme ich es in Kauf zwei 
Klammern mehr zu schreiben.
1
int16_t erg = (int16_t)  ( ( (int32_t) a  *  b ) / 100 );

von Klaus (Gast)


Lesenswert?

Japp, deine Version is korrekt. Ich wollt nur auf den Fehler in Jupps 
Beitrag hinweisen.

von Simon K. (simon) Benutzerseite


Lesenswert?

Klaus schrieb:
> Japp, deine Version is korrekt. Ich wollt nur auf den Fehler in Jupps
> Beitrag hinweisen.

Dann noch einen schönen Advent! :-)

von Klaus (Gast)


Lesenswert?

Ja, dir natürlich auch einen schönen 4. Advent :D

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.