Forum: Mikrocontroller und Digitale Elektronik Mathe mit dem ATMega32


von haribo (Gast)


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

von Sebastian (Gast)


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.

von G ast (Gast)


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

von Sebastian (Gast)


Lesenswert?

Tippfehler, es muss heißen:

Ist pos1-pos0 jederzeit positiv ? Wenn nicht, uint32_t in int32_t 
ändern.

von Oliver (Gast)


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

von Jean (Gast)


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ß

von 1323 (Gast)


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

von Michael U. (amiga)


Lesenswert?

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

von Michael B. (bubi)


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

von haribo (Gast)


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?

von haribo (Gast)


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++;

von haribo (Gast)


Lesenswert?

oder ist das alles falsch? :)

von haribo (Gast)


Lesenswert?

in dem beitrag 2 weiter oben natürlich den letzten cast auf uint16_t 
natürlich weglassen

von Karl H. (kbuchegg)


Lesenswert?

> Genauer wäre somit:
>
> double temp;


Man kann auch einfach die Division noch eine Stufe nach rechts schieben.

 a/b + c   <==>  ( a + c ) / b

von Vlad T. (vlad_tepesch)


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.

von 1323 (Gast)


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 ;)

von Michael G. (linuxgeek) Benutzerseite


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

von haribo (Gast)


Lesenswert?

das gilt aber nur für b=1 oder?

von haribo (Gast)


Lesenswert?

@ michael....

bitte lies den post um 9:33 von mir

von G ast (Gast)


Lesenswert?

> Man kann auch einfach die Division noch eine Stufe nach rechts schieben.
> a/b + c   <==>  ( a + c ) / b

Irgendwas stimmt da aber nicht ;)

von Michael G. (linuxgeek) Benutzerseite


Lesenswert?

> 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

So isses richtig:

270-90 = 180
180*20000 mod 2^16 = 61056
61056/12000 = 5 + 90 = 95

Michael

von haribo (Gast)


Lesenswert?

@ michael....

lies meinen post um 9:33

:) dort wirst du feststellen dass ich in der angabe einen fehler gemacht 
habe....nämlich die werte von pos0 und pos1 vertauscht...

:)

von haribo (Gast)


Lesenswert?

sorry time0 und time1 natürlich..jetzt wirds verwirrend :-/ :)

von P. S. (Gast)


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.

von Karl H. (kbuchegg)


Lesenswert?

G ast schrieb:
>> Man kann auch einfach die Division noch eine Stufe nach rechts schieben.
>> a/b + c   <==>  ( a + c ) / b
>
> Irgendwas stimmt da aber nicht ;)

Da hast du recht. War in Gedanken schon beim Kaffee

  a/b + c   <==>  ( a + c * b ) / b

von Michael B. (bubi)


Lesenswert?

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!

(PS: alles aus laiensicht...)

von Udo. R. S. (Gast)


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!

von Karl H. (kbuchegg)


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

von Walter (Gast)


Lesenswert?

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

von Walter (Gast)


Lesenswert?

mist, zu langsam ...

von Michael B. (bubi)


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

von Vlad T. (vlad_tepesch)


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.

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.