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. :)
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.
> 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
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
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ß
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 ^^
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...!
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?
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 ;)
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
> 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
@ 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...
:)
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.
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
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...)
> 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!
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
>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.
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
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.