Hallo,
ich habe da in meinem Programm-Code etwas interessantes festgestellt,
dass ich gerade nicht nachvollziehen kann:
Wenn ich von einer uint16_t Variable mit dem Wert z.B. 700, den Wert
einer uint32_t Variable mit dem Wert 4294967295 abziehe, dann erhalte
ich 699.
1
uint16_tx=700;
2
uint32_ty=4294967295;
3
x=x-y;
4
// x = 699
Das ist für mich gerade auch so passend, aber wieso wird dabei immer
genau -1 abgezogen? Ich habe versucht dies mit binärer Subtraktion
nachzuvollziehen, aber ganz verstehe ich es irgendwie nicht.
Danke für jeden Hinweis
hawky schrieb:> aber wieso wird dabei immer genau -1 abgezogen?
Deine Zahl ist nicht zufällig, sondern 2^32-1 = 0xffffffff-1 =
4294967296-1
Es ist unwahrscheinlich, dass 1 abgezogen wird und 699 rauskommt. Eher
wird (wie Du schriebst) -1 abgezogen und es kommt 701 raus.
Tu Dir den Gefallen und spiele das mit uint8 und uint16 durch, die Zahl
dazu heisst 65535 (oder 0xffff)
A. S. schrieb:> Deine Zahl ist nicht zufällig, sondern 2^32-1 = 0xffffffff-1 => 4294967296-1
Fast, aber nicht ganz:
- 4294967295 = 0xFFFFFFFF = 2^32 - 1
- 2^32 = 4294967296 = 0x100000000
Das mit Problem dürfte eher mal wieder folgendes sein:
1
-(uint16_t)x=(int)x-(int)y;
Und "unsigned 0xFFFFFFFF" entspricht hierbei "singend -1" wenn es sich
bei "int" um einen 32Bit Wert handelt
hawky schrieb:> aber ganz verstehe ich es irgendwie nicht.
Das ergibt nur bei Addition von y oder der Subtraktion von -y Sinn.
Nicht aber bei der Subtraktion von y. Da die Rechnung vorzeichenlos
erfolgt, ist das Überlaufverhalten klar definiert.
Irgend W. schrieb:> Und "unsigned 0xFFFFFFFF" entspricht hierbei "singend -1" wenn es sich(prx) A. K. schrieb:> uint32_t wird nicht in int gerechnet, es sei denn int hätte mehr> als 32> Bits. Das ist aber auf keiner gängigen Plattform der Fall.
also diese uint32_t y variable wird irgendwo in meinem Code auch
dekrementiert, und 0 - 1 ergibt dann halt den überlauf bzw. diese
0xFFFFFFFF.
hmm ok, aber irgendwie verstehe ich es trotzdem noch nicht ganz, wieso
wird mit signed gerechnet?
(prx) A. K. schrieb:> Das ergibt nur bei Addition von y oder der Subtraktion von -y Sinn.> Nicht aber bei der Subtraktion von y. Da die Rechnung vorzeichenlos> erfolgt, ist das Überlaufverhalten klar definiert.
ach sorry ja, also ganz korrekt schaut es so aus:
hawky schrieb:> hmm ok, aber irgendwie verstehe ich es trotzdem noch nicht ganz, wieso> wird mit signed gerechnet?
Wie A. K. schon gesagt, wird nicht in signed gerechnet. Du solltest, wie
einige andere hier im Thread ein gutes C-Buch lesen. Dort sollte so
etwas wie "usual arithmetic conversions" auftauchen, wo das erklärt
wird.
hawky schrieb:> ach sorry ja, also ganz korrekt schaut es so aus:> uint16_t x = 700;> uint32_t y = 4294967295;
Gerechnet wird aber mit
uint32_t(700);
Dank der impliziten Konvertierungsregeln.
Und dann ist das Ergebnis größer als uint32_t fassen kann.
Ein Überlauf, so stattfindet.
Mombert H. schrieb:> hawky schrieb:>> hmm ok, aber irgendwie verstehe ich es trotzdem noch nicht ganz, wieso>> wird mit signed gerechnet?>> Wie A. K. schon gesagt, wird nicht in signed gerechnet. Du solltest, wie> einige andere hier im Thread ein gutes C-Buch lesen. Dort sollte so> etwas wie "usual arithmetic conversions" auftauchen, wo das erklärt> wird.
Hier ist kein C Buch gefragt, sondern Basis-Ganzzahl-Arithmetik -
insbesondere Modulo (bei uns als Division mit Rest bekannt)
Arithmentik...
Die Addition von einer uint Zahl wo alle Bits gesetzt sind, entsprich
faktisch einer Subtraktion von 1 in solch einer Zahlengeraden.
Fun fact: Intern kann ein AVR nur konstanten von variablen subtrahieren,
nicht addieren (nur zwei variablen - register - können miteinander
addiert werden). Wenn man also i++ oder i-- schreibt, wird das vom
compiler in den gleichen Befehl umgesetzt SUBI i, -1 im ersten Fall - in
hex: SUBI i, 0xFF. Und im zweiten Fall SUBI i, 1 bzw SUBI i, 0x01.
hawky schrieb:> uint32_t y = 4294967295;
Im Prinzip hat man schon hier ein Problem. Die zugewiesene Zahl ist vom
Typ int, aber der Wert passt da gar nicht rein.
Man könnte auch so schreiben:
unsigned schrieb:> Im Prinzip hat man schon hier ein Problem. Die zugewiesene Zahl ist vom> Typ int,
Ist sie nicht auf jedem System
int kann auch 64 Bit breit sein, dann passt das schon
Auf 8Bit:
Wird automatisch zu Long aufgeweitet!
Richtiger wäre dann
uint32_t y = 4294967295UL;
Stell dir vor nach jeder Rechenoperation rechnest du "mod 4". Nimm eine
Zahl und addiere 3. Jetzt hast du eins weniger als die zahl. Jetzt
stelle dir vor, du rechnest erst mod 8, dann mod 4. Nichts ändert sich.
Das ist weil 8 ein vielfaches von 4 ist. Mit 8 mod 2 geht's halt exakt
2mal im kreis rum. Ist im Grunde das selbe hier, nur mit etwas grösserem
Wertebereich.
hawky schrieb:> ach sorry ja, also ganz korrekt schaut es so aus:> uint16_t x = 700;> uint32_t y = 4294967295;> x = x + y;> // x = 699
Ja. Wie mehrfach beschrieben. Und jetzt zieh das Beispiel auf 8/16 oder
8/8 bit und
hawky schrieb:> versucht dies mit binärer Subtraktion nachzuvollziehen,
EAF schrieb:> int kann auch 64 Bit breit sein, dann passt das schon
Ist aber selten, in Windows und Linux ist "int" 32 Bits breit:
https://en.wikipedia.org/wiki/64-bit_computing#64-bit_data_models
Sollte man natürlich trotzdem fürs Verständnis der Sprache im Auge
behalten, oder um sich von vorneherein portable Programmierung
anzugewöhnen.
unsigned schrieb:> Oder korrekt:1uint32_t y = 4294967295U;
Ohne das U ist’s genauso korrekt, denn genauso, wie es in C integer
Promotion rules gibt, gibt es auch Regeln, welchen Typ literate
Konstanten bekommen. Und ist wird 4294967295 kein int.
Oliver
unsigned schrieb:> Im Prinzip hat man schon hier ein Problem. Die zugewiesene Zahl ist vom> Typ int, aber der Wert passt da gar nicht rein.
Eine dezimal geschriebene Zahl ist vom kleinsten Typ, in den sie
passt, in der Reihenfolge int => long => long long. Zahlen auf anderer
Basis, wie etwa Hex, haben eine andere Reihenfolge.
Da die Zahl 4294967295 in "int" nicht reinpasst, ist sie je nach
Plattform eben nicht vom Typ "int", sondern auch ohne Suffix automatisch
vom Typ "long" oder "long long".
(prx) A. K. schrieb:> Da die Zahl 4294967295 in "int" nicht reinpasst, ist sie je nach> Plattform eben nicht vom Typ "int", sondern auch ohne Suffix automatisch> vom Typ "long" oder "long long".
Fun fact: Aufgrund des unterschiedlichen Datenmodells ist das in Windows
der Typ "long long" und in Linux "long". Siehe Link oben.
unsigned schrieb:> Man könnte auch so schreiben:uint32_t y = -1;
Macht auf jeden Fall Sinn, einer vorzeichenlosen Variablen den Wert
einer negative Zahl zuzuweisen :-(
Statt sich in impliziten Konvertierungen zu verstricken, sollte man das
besser sauber formulieren und einen Type Cast hinschreiben.
unsigned schrieb:> Man könnte auch so schreiben:uint32_t y = -1;
De facto ja, de jure nein. Solange C noch historisch existierende
Plattformen mit Einerkomplementdarstellung zulässt, kann das in die Hose
gehen. Denn darin hat -1 nicht das Bitpattern 1111_1111, sondern
1111_1110.
Richard S. schrieb:> Mombert H. schrieb:>> hawky schrieb:>>> hmm ok, aber irgendwie verstehe ich es trotzdem noch nicht ganz, wieso>>> wird mit signed gerechnet?>>>> Wie A. K. schon gesagt, wird nicht in signed gerechnet. Du solltest, wie>> einige andere hier im Thread ein gutes C-Buch lesen. Dort sollte so>> etwas wie "usual arithmetic conversions" auftauchen, wo das erklärt>> wird.>> Hier ist kein C Buch gefragt, sondern Basis-Ganzzahl-Arithmetik -> insbesondere Modulo (bei uns als Division mit Rest bekannt)> Arithmentik...
Natürlich ist hier Ganzzahl-Arithmetik gefragt, nur was genau? Hat sich
Irgend W. (Firma: egal) (irgendwer) nur verrechnet, oder hat er etwas
falsches gerechnet? Hat sich der TO nur verrechnet, oder hat er nicht
verstanden was er rechnen muss?
> Die Addition von einer uint Zahl wo alle Bits gesetzt sind, entsprich> faktisch einer Subtraktion von 1 in solch einer Zahlengeraden.
Bis auf all die Fälle, wo es etwas anderes bedeutet.
Mombert H. schrieb:> Hat sich Irgend W. (Firma: egal) (irgendwer) nur verrechnet, oder hat er> etwas falsches gerechnet?
Er hat gar nichts gerechnet
> Hat sich der TO nur verrechnet, oder hat er> nicht verstanden was er rechnen muss?
Er hat zwar + und - verwechselt, ansonsten aber nichts zu seinem Problem
geschrieben. Also was er erwartet hätte oder wie er gerechnet hat, ob er
den Überlauf bei unsigned kennt oder ob sich seine Frage erledigt hat,
wenn er + statt - rechnet..
Das Problem lässt sich ja auch mit dezimalen Zahlen bewerkstelligen,
welche eine begrenzte Stellenzahl haben.
Maximal vierstellig darstellbare Zahl = 9999
Nun soll von einer zweistelligen Zahl diese Zahl abgezogen werden:
39 - 9999 = -9960
Da wir aber keine negativen Zahlen darstellen, muss einaml der gesamte
darstellbare Bereich (also die erste nicht mehr darstellbare Zahl)
addiert werden - das passiert bei einem Überlauf von selbst:
-9960 + 10000 = 40
Oder nochmal anders:
Du hast einen 3-stelligen km Zähler.
Fährst Du 1000km, steht das selbe wie vorher auf dem Zähler.
Fährst Du 1000km rückwärts, ebenfalls.
Fährst Du nur 999km rückwärts, steht 1km mehr drauf als vorher.
Nun sollte man es aber begriffen haben ...
Gruß
Jobst