mikrocontroller.net

Forum: Mikrocontroller und Digitale Elektronik Mathe mit dem ATMega32


Autor: haribo (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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. :)

Autor: Sebastian (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: G ast (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Sebastian (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Tippfehler, es muss heißen:

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

Autor: Oliver (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Jean (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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ß

Autor: 1323 (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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 ^^

Autor: Michael U. (amiga)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Michael B. (bubi)
Datum:

Bewertung
0 lesenswert
nicht 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...!

Autor: haribo (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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?

Autor: haribo (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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++;

Autor: haribo (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
oder ist das alles falsch? :)

Autor: haribo (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
in dem beitrag 2 weiter oben natürlich den letzten cast auf uint16_t 
natürlich weglassen

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Vlad Tepesch (vlad_tepesch)
Datum:

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

fang bloß nicht an da doubles rein zufummeln!
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.

Autor: 1323 (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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 ;)

Autor: Michael G. (linuxgeek) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: haribo (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
das gilt aber nur für b=1 oder?

Autor: haribo (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
@ michael....

bitte lies den post um 9:33 von mir

Autor: G ast (Gast)
Datum:

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

Irgendwas stimmt da aber nicht ;)

Autor: Michael G. (linuxgeek) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: haribo (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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...

:)

Autor: haribo (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
sorry time0 und time1 natürlich..jetzt wirds verwirrend :-/ :)

Autor: P. S. (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Michael B. (bubi)
Datum:

Bewertung
0 lesenswert
nicht 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...)

Autor: Udo. R. S. (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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!

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Walter (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Walter (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
mist, zu langsam ...

Autor: Michael B. (bubi)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Vlad Tepesch (vlad_tepesch)
Datum:

Bewertung
0 lesenswert
nicht 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

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.