Forum: Mikrocontroller und Digitale Elektronik uint16_t Subtraktion mit uint32_t


von hawky (Gast)


Lesenswert?

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_t x = 700;
2
uint32_t y = 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

von A. S. (Gast)


Lesenswert?

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)

von c-hater (Gast)


Lesenswert?

Das Ergebnis läuft halt über. Es passt nicht in uint32_t.

von Mombert H. (mh_mh)


Lesenswert?

c-hater schrieb:
> Das Ergebnis läuft halt über. Es passt nicht in uint32_t.

Kannst du etwas genauer erklären wie da x=699 rauskommt?

von Irgend W. (Firma: egal) (irgendwer)


Lesenswert?

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

von (prx) A. K. (prx)


Lesenswert?

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.

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


Lesenswert?

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.

von hawky (Gast)


Lesenswert?

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?

von hawky (Gast)


Lesenswert?

(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:
1
uint16_t x = 700;
2
uint32_t y = 4294967295;
3
x = x + y;
4
// x = 699

Beitrag #6928351 wurde vom Autor gelöscht.
von (prx) A. K. (prx)


Lesenswert?

hawky schrieb:
> wieso wird mit signed gerechnet

Wird es nicht.

von Mombert H. (mh_mh)


Lesenswert?

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.

von EAF (Gast)


Lesenswert?

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.

von Richard S. (rscheff)


Lesenswert?

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.

von unsigned (Gast)


Lesenswert?

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:
1
uint32_t y = -1;

Oder korrekt:
1
uint32_t y = 4294967295U;

von EAF (Gast)


Lesenswert?

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;

von DPA (Gast)


Lesenswert?

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.

von Achs (Gast)


Lesenswert?

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,
1
uint8_t x = 7;
2
uint8_t y = 255;
3
x = x + y;
4
// x = 6

von (prx) A. K. (prx)


Lesenswert?

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.

: Bearbeitet durch User
von Oliver S. (oliverso)


Lesenswert?

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

von (prx) A. K. (prx)


Lesenswert?

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".

von Rolf M. (rmagnus)


Lesenswert?

Wenn man sie explizit vom Typ uint32_t machen will, wäre das die 
passende Variante:
1
uint32_t y = UINT32_C(4294967295);

von (prx) A. K. (prx)


Lesenswert?

(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.

: Bearbeitet durch User
von Wolfgang (Gast)


Lesenswert?

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.

von (prx) A. K. (prx)


Lesenswert?

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.

von Mombert H. (mh_mh)


Lesenswert?

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.

von achs (Gast)


Lesenswert?

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..

von Jobst M. (jobstens-de)


Lesenswert?

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

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.