Mathe mit dem ATMega32

Gast #1522439
Lesenswert?

Hallo,

ich baue gerade eine kleine Steuerung mit einem ATMega32 auf. Zum 
Programmieren benutze ich das AVR-Studio mit dem GNU C-COmpiler, 
programmiere also in C.

Bei Folgendem Code bekomme ich aber Probleme:

uint16_t neigung, pos0, pos1, time0, time1;

neigung = (pos1-pos0)*time1/time0 + pos0;

Die Variable neigung enthält nicht das korrekte Ergenbis sondern scheint 
stets überzulaufen.
Es sei angemerkt dass sich die Variablen time1 und time0 in einem 
Bereich von 0 bis 32000 befinden können. time0 ist dabei mindetens so 
groß wie time1.
pos1 und pos0 liegen immer zwischen 0 und 359;

Ein Beispiel: Nimmt man an:

pos1 = 270;
pos0 = 90;
time1 = 20000;
time0 = 12000;

So müsste neigung = 198 sein. Leider stimmt das Ergebnis meist nicht, 
nur ab und zu. Lediglich wen die Variablen time1 und time0 recht klein 
sind ist das Ergebnis korrekt.
Meine Schlssfolgerung: Bei der Multiplikation von (pos1-pos0)*time1 wird 
das Ergebnis zu hoch und es kommt zu einem Überlauf. Wie könnte man das 
Poblem nun lösen?

Deklariere ich neigung als uint32_t ändert das scheinbar auch nichts. 
Wie ist das genau mit mathematischen Berechnungen in C?

Bitte um eine Erklärung oder Hilfe wie man das Problem lösen kann. :)
Gast #1522445
Lesenswert?

Erweitere den Datentyp für die BErechnung auf 32bit und caste das 
Ergebnis wieder auf 16 bit zurück. Wenn du nur "neigung" als 32bit 
definierst, wird der Ausdruck aber trotztdem als 16 bit ausgewertet.

neigung = (uint16_t)((uint32_t)(pos1-pos0)*time1/time0 + pos0);

Ist jederzeit ausgeschlossen, dass pos1-pos0 >= 0 ? Wenn nicht, uint32_t 
in int32_t ändern.
Gast #1522446
Lesenswert?

> Wie könnte man das Poblem nun lösen?
Formel umformen oder entsprechenden großen Datentyp benutzen.

> Deklariere ich neigung als uint32_t ändert das scheinbar auch nichts.
Was ändert sich scheinbar?

Rechne dir mal von Hand aus wieviel größer dein Datentyp sein muss, nur 
rumprobieren kann zum Ziel führen, muss es aber nicht.

Mit deinen Beispielwerten merkt man schnell:
270-90*20000=3600000 <-- das ist bereits zu viel für einen kleinen int16
Gast #1522448
Lesenswert?

Ganze Zahlen sind ganze Zahlen sind ganze Zahlen. Gerechnet wird von 
rechts nach links. Und Punktrechnung geht vor Strichrechnung.

Damit ergibt

(270-90) * 20000 / 12000 + 90

nach der Multiplikation der Klammer mit 20.0000 einen satten Überlauf.

Je nachdem, wie genau das Ergebnis werden soll, musst du entweder deine 
Faktoren so skalieren, daß du immer im uint16_t-Wertebereich bleibst, 
oder  auf 32-Bit-Variablen ausweichen.

Oliver
Gast #1522459
Lesenswert?

haribo schrieb:
> Hallo,
Hi

> uint16_t neigung, pos0, pos1, time0, time1;
> neigung = (pos1-pos0)*time1/time0 + pos0;
Ziemlich ungenaue Rechnung wird das !

> Die Variable neigung enthält nicht das korrekte Ergenbis sondern scheint
> stets überzulaufen.
> Es sei angemerkt dass sich die Variablen time1 und time0 in einem
> Bereich von 0 bis 32000 befinden können. time0 ist dabei mindetens so
> groß wie time1.
> pos1 und pos0 liegen immer zwischen 0 und 359;

> Ein Beispiel: Nimmt man an:
>
> pos1 = 270;
> pos0 = 90;
> time1 = 20000;
> time0 = 12000;
Also was denn nun ich denke time0 ist immer gröser time ???

> So müsste neigung = 198 sein. Leider stimmt das Ergebnis meist nicht,
> nur ab und zu. Lediglich wen die Variablen time1 und time0 recht klein
> sind ist das Ergebnis korrekt.
Also ich komme auf 390 !

> Meine Schlssfolgerung: Bei der Multiplikation von (pos1-pos0)*time1 wird
> das Ergebnis zu hoch und es kommt zu einem Überlauf. Wie könnte man das
> Poblem nun lösen?
Einfach alle als long deklarieren oder in Einzelrechnungen aufteilen.
Z.B so posdef = pos1 - pos0  und  Timedef = time1/time0

> Deklariere ich neigung als uint32_t ändert das scheinbar auch nichts.
> Wie ist das genau mit mathematischen Berechnungen in C?
Logisch weil die anderen Zahlen ja noch int sind.

> Bitte um eine Erklärung oder Hilfe wie man das Problem lösen kann. :)
Siehe oben.

Gruß
Gast #1522461
Lesenswert?

Deine Rechnung macht das:

270-90 = 180
20000/12000=1  // beim Teilen von INT wird immer abgerundet.
180 * 1 = 180
180+90 = 270

wobei ich mir grad nicht sicher bin ob das Mal nicht vor dem Geteilt 
kommt

also 180*20000 = 3600000 //sollte in 16bit passen
360000/12000= 300
300+90 = 390


wie du aber rechnerisch mit den Werten auf 198 kommst erschießt sich mir 
nicht ganz ^^
#1522479
Lesenswert?

Vl steh ich auch grad auf dem Schlauch...

> Bereich von 0 bis 32000 befinden können. time0 ist dabei mindetens so
> groß wie time1.

Das würde heißen bei
> neigung = (pos1-pos0)*time1/time0 + pos0;

das time1/time0 eigentlich nur 1 oder 0 sein kann...
Würde sich dann aber mit seiner Angabe wiedersprechen wo time0 
wesentlich kleiner als time1 ist...!
Gast #1522487
Lesenswert?

Oh...ein Fehler in der Angabe....die Division von time1/time0 ist immer 
unter 1 oder gleich eins....also time1 ist immer kleiner als time0. 
Entschuldigung für diesen Fehler:
pos1-pos0 ist jederzeit positiv!  :)

Somit wäre:


neigung = (270-90)*12000/20000 + 90;
neigung = 180*12000/20000 + 90;
neigung = 2160000/20000 + 90; // hier scheinbar der Überlauf weil 
2160000 zu hoch
neigung = 108 + 90;
neigung = 198;

Nach Sebastians antwort müsste als das ganze so funktionieren:

neigung = (uint16)((((uint_32)(pos1-pos0))*((uint_32)time1))/time0)+90);

Oder?
Gast #1522492
Lesenswert?

Was natürlich die Nachkommas einfach abschneidet.

Genauer wäre somit:

double temp;
temp = 
(uint16)((((double)(pos1-pos0))*((double)time1))/((double)time0))+90);
neigung = temp;
if((temp-neigung)*10>=5)
neigung++;
#1522501
Lesenswert?

haribo schrieb:
> (uint16)((((double)(pos1-pos0))*((double)time1))/((double)time0))+90);

fang bloß nicht an da doubles rein zufummeln!
1
neigung = ((uint16_t)( (((uint32_t)(pos1-pos0))*time1)/time0) + pos0;

das sollte schon reichen.
Das Problem ist, dass standardmäßig in uint16 gerechet wird, was 
natürlich überläuft.
Gast #1522502
Lesenswert?

Michael U. schrieb:
> Hallo,
>
> 1323 schrieb:
>> also 180*20000 = 3600000 //sollte in 16bit passen
>
> aber nur, wenn es sehr sehr große Bits sind...
>
> Gruß aus Berlin
> Michael

lol stimmt, sorry hab da was verdreht ^^ 16 bit sind ja nur 64k

also auf 32bit casten ;)
Persönliche Seite #1522504
Lesenswert?

haribo schrieb:

> Bei Folgendem Code bekomme ich aber Probleme:
>
> uint16_t neigung, pos0, pos1, time0, time1;
>
> neigung = (pos1-pos0)*time1/time0 + pos0;
>
> Die Variable neigung enthält nicht das korrekte Ergenbis sondern scheint
> stets überzulaufen.
> Es sei angemerkt dass sich die Variablen time1 und time0 in einem
> Bereich von 0 bis 32000 befinden können. time0 ist dabei mindetens so
> groß wie time1.

Problem 1: Division durch Null tut nicht so gut.
Problem zwei: Du rechnest mit Integern aber verwendest eine Division.

> pos1 und pos0 liegen immer zwischen 0 und 359;
>
> Ein Beispiel: Nimmt man an:
>
> pos1 = 270;
> pos0 = 90;
> time1 = 20000;
> time0 = 12000;
>
> So müsste neigung = 198 sein. Leider stimmt das Ergebnis meist nicht

270-90 = 180
time1/time0 = 20000/12000 = 1.6 = 1

180*1 = 180 + 90 = 270

Michael
Gast #1522522
Lesenswert?

Gerade Anfaengern wuerde ich empfehlen, solche Berechnungen schoen in 
einzelne Zeilen aufzuteilen. Dann kriegt man einen schoenen Ueberblick 
ueber die genaue Reihenfolge und kann auch beim Debuggen jeden Wert 
einzeln ausgeben und auch fuer die Zwischenwerte die Datentypen einfach 
angeben. Und der Lerneffekt duerfte hoeher sein, als bei der 
Zurechtfummelei.
Gast #1522569
Lesenswert?

> a/b + c   <==>  ( a + c * b ) / b
Das bringt Dir aber nur dann etwas, wenn a und c in der gleichen 
Größenordnung liegen und Du Bedenken hast, daß durch das Abschneiden des 
nichtganzzahligen Bereichs der Fehler von a/b zu groß wird.
Du hast dann aber das Problem daß falls b und c relativ groß sind Du 
wieder einen Überlauf erhälst.
Den angegebenen Zahlen nach dürfte aber 32Bit ausreichend groß sein.

@haribo
Merke: bei Ganzzahlarithmetik die Division gezielt so spät wie möglich 
durchführen um den Fehler zu minimieren und vorher eine Abschätzung wie 
groß Zwischenergebnisse werden können!
#1522583
Lesenswert?

Michael B. schrieb:
> betrachten wir mal nur time1/time0
>
> er sagt er habe die Werte verdreht..:
>
> time0 = 20000
> time1 = 12000
>
> 12000/20000 = 0.6.. bei int16... soweit ich weiß wird abgerundet, also
> ist das Ergebniss = pos0 = 90!

Aber nicht in seinem Fall

   (pos1-pos0)*time1/time0

wird gerechnet als

    ((pos1-pos0)*time1)/time0
#1522598
Lesenswert?

Da lag also der Hund :) wusste irgendwas is da noch

Ist das vom Compiler vorgeben?
Ich kann mich noch an meine Schulzeit errinern da habe ich genau das 
ausgenutzt...

Die Klammer hat bewirkt das der Compiler alles in Teilglieder zerlegt 
hat und die dann erst untereinander verrechnet hat..
also

"(pos1-pos0)" * "time1/time0" + "pos"

Die Multiplikation kam nach dem dividieren...

Beispiel: pos0*time1/time0 wurde aber als "pos0*time1"/time0 
gerechnet...
Das hat mich damals zur verzweiflung getrieben.. (natürlich in einem 
ganz anderen fall)
Assamblerlistning hat mir damals geholfen.. :)

BTW war ein 8052...aber fragt mich nicht nach dem Compiler...

€ Die Beispiele sind schlecht gewählt.. Es war so das eine Klammer das 
"von links nach rechts" rechnen aus dem Ruder gebracht hat und es in 
"Glieder" unterteilt wurde
#1522781
Lesenswert?

Walter schrieb:
>>betrachten wir mal nur time1/time0
> das sieht Ein C-Compiler aber anders,
> er rechnet nämlich von links nach rechts
> also erst
> (pos1-pos0)*time1
> und dann wird mit
> /time0 dividiert.

so rum ist es ja auch richtig.
wenn du erst teilen würdest, würde das Ergebnis recht ungenau werden.

beispiel:
100*3/2
entweder
(100*3) / 2 = 150
100*(3/2)   = 100
besser ist es erst zu multiplizieren.

natürlich muss man daran denken, dass der Compiler nicht automatisch den 
Datentyp verbreitert und selbst dafür sorgen, dass das die 
multiplikation mit einem größeren Datentyp gerechnet wird.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren