www.mikrocontroller.net

Forum: Compiler & IDEs Rundungsfehler trotz cast?


Autor: Martin (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo Leute.

Ich habe ein Problem beim lösen einer (Geraden)Gleichung.

Sie ist vom typ y=kx+d

y folgendermaßen berechnet:

Ein Zähler "time" zählt von 0...99:
uint8_t result;

uint8_t x1=0;
uint8_t x2=100;

uint8_t y2=100;
uint8_t y1=0;

for (time=0; time<x2; time++) {

result = ( (y2-y1) * 10 * (int32_t)time) ) / ( (x2-x1) * (int32_t)10 ) )

}


Dabei ist das Ergebnis immer einer weniger. Das liegt sicher am Runden. 
Ich dachte, dass ich mit dem cast auf int32 alles abfangen könnte. 
Scheinbar ist dem aber nicht so. Wie kann man das Problem lösen?

Autor: Lasse S. (cowz) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Das hast du doch sicherlich aus irgendeinem Code rauskopiert, oder?

Zumindest ist das rein syntaktisch falsch, eine Klammer wird nie 
geöffnet.

Außerdem ist die Schleife so recht sinnlos, man könnte auch einfach 
time=99 setzen und die Berechnung (die dann nur noch aus Konstanten 
besteht) nur einmal berechnen lassen.

Bei mir im Taschenrechner kommt da 99 raus, was kommt denn bei deinem 
Code raus?

Gruß
Lasse

PS: Zumindest die 10 könnte man in jedem Fall kürzen.

Autor: Johann L. (gjlayde) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ganzzahl-Division rundet eben zu 0 hin. Um a/b zu bekommen hilft also 
u.U (a+a/2)/b zu berechnen. Das Erweitern mit 10 bringt die wie bereits 
geschrieben überhaupt nix.

Falls du wirklich nacheinander alle Punkte auf der Strecke willst -- was 
durch den Variablenname 'time' zumindest nahegelegt wird -- ist 
Bresenham dein Freund.

   http://de.wikipedia.org/wiki/Bresenham-Algorithmus

Der braucht auch keine Division bzw. berechnet diese implizit, indem er 
Zähler und Nenner mitführt anstatt immer einen Quotienten auszurechnen.

Autor: Martin (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ich möchte im Prinzip auf einer Gerade alle Punkte berechnen können- den 
ersten, den letzten und alle dazwischen.

Die Punkte werden für eine Positionierung im weitesten Sinne verwendet.

Autor: nicht "Gast" (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Was spricht dagegen, alle weiteren Punkte aus dem Ursprung (erster 
Punkt) und der Steigung zu berechnen?

Gast

Autor: Rolf Magnus (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
nicht "Gast" schrieb:
> Was spricht dagegen, alle weiteren Punkte aus dem Ursprung (erster
> Punkt) und der Steigung zu berechnen?

Daß die Steigung auch mehr oder weniger weit abweicht und man dann 
sonstwo rauskommt.

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

Bewertung
0 lesenswert
nicht lesenswert
Martin schrieb:

> Das liegt sicher am Runden.

Eigentlich nicht.
Es liegt daran, dass es sich um eine Ganzzahldivision handelt und dort 
wrden weder Nachkommastellen berechnet, noch wird gerundet.

Autor: Martin (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Karl heinz Buchegger schrieb:
> Eigentlich nicht.
>
> Es liegt daran, dass es sich um eine Ganzzahldivision handelt und dort
>
> wrden weder Nachkommastellen berechnet, noch wird gerundet.

Und wie lös ich das Problem?

Mein "Trick" wäre ja, beim letzten Wert der for schleife einfach zu 
sagen
 result = y2 
 aber das ist schon komisch...

Autor: Rolf Magnus (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert

Autor: adfix (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
>Mein "Trick" wäre ja, beim letzten Wert der for schleife einfach zu sagen
> result = y2
> aber das ist schon komisch...

Ich meine, langsam Deine Frage zu verstehen.
Du hast eine Gerade, die von Punkt X:0 bis X:100 geht. Also über 101 
Punkte.
Zählst aber nur von 0..99, also über 100 Punkte.

Dann ist alles richtig. Kein Rundungsfehler.
Dann musst Du nur:
uint8_t x2=99; 

setzen.

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

Bewertung
0 lesenswert
nicht lesenswert
Martin schrieb:

>> wrden weder Nachkommastellen berechnet, noch wird gerundet.
>
> Und wie lös ich das Problem?

Indem zu zb selber für eine Rundung sorgst.

Nach dem Muster:
a / b   als ganzzahlige Division gerundet wird als  (a + b/2) / b 
gerechnet.
  uint32_t deltaX = x2 - x1;
  uint32_t deltaY = y2 - y1;

  for( time = 0; time < x2; time++ ) {
    result = ( deltaY * time + deltaX / 2 ) / deltaX;

Man kann sich das so vorstellen:
angenommen du hättest eine Gleitkommadivision

    c = a / b

dann hat c Nachkommastellen. Weist du c einem int zu

    int d = c

dann werden Nachkommastellen einfach abgeschnitten. Aus 1.9 wird so 1
Um das zu vermeiden musst du runden

     int d = c + 0.5

Die Rundungskorrektur von 0.5 sorgt jetzt dafür, dass das Ergebnis 
soweit erhöht wird, dass alle Werte > x.5 über die nächste ganze Zahl 
kommen und wenn dann die Nachkommastellen einfach weggeworfen werden, 
kommt x+1 raus.
Aus 1.6 wird so 2 und nicht 1

Und jetzt wird rückwärts eingesetzt

    int d = a / b + 0.5

wir wollen die Kommazahl loswerden.

     int d = a / b + 1 / 2

auf gemeinsamen Nenner gebracht

     int d = a / b + b / 2b

     int d = a / b + ( b/2 ) / b

     int d = ( a + b/2 ) / b

voila. Du Rundungskorrektur wurde soweit nach vor gezogen, dass alles 
ganzzahlig ist. Auch die Division muss jetzt nicht mehr in Gleitkomma 
erfolgen sondern kann ganzzahlig gemacht werden.

Soweit zum runden. Allerdings: mit deinen originalen Zahlen, ist das 
Problem nicht das runden :-)
adfix hat, denk ich, auch recht. Aber sein Fix ist falsch.
Wenn du den Endpunkt erreichen willst, dann muss es lauten

   for( time = 0; time <= x2; time++ ) {

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.